How to fit object inside canvas in three js

Viewed 847

I'm currently using threejs library to create 3d objects, but unfortunately I cannot fit the object inside the canvas. It's always overflowing outside the canvas if the object is a bit long. Please see my code in JSFiddle.

Script

import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r127/build/three.module.js'

function generate3DBox(selector, angle, boxDepth, boxWidth, boxHeight) {
    
    let allowedAngles = [
        'standard-0',
        'standard-90',
        'turn-up-0',
        'turn-up-90',
        'turn-side-0',
        'turn-side-90'
    ]
  
    if(allowedAngles.indexOf(angle) < 0) {
        console.log("Angle is incorrect")
        return false
    }

    const canvas = document.querySelector(selector)
    const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })

    const canvasWidth = canvas.getBoundingClientRect().width
    const canvasHeight = canvas.getBoundingClientRect().height
    const minSize = Math.min(...[boxDepth, boxWidth, boxHeight])
    const maxSize = Math.max(...[boxDepth, boxWidth, boxHeight])
  
    const aspect = canvasWidth / canvasHeight
    const fov = 75
    const near = 0.1
    const far = 1000
  
    let cameraZoom = 1
    let cameraPosition = {
        left: canvasWidth / -2,
        right: canvasWidth / 2,
        top: canvasHeight / 2,
        bottom: canvasHeight / -2
    }

    const camera = new THREE.OrthographicCamera(
        cameraPosition.left,
        cameraPosition.right,
        cameraPosition.top,
        cameraPosition.bottom,
        near,
        far
    )

    camera.position.z = maxSize + minSize
    camera.zoom = cameraZoom

    camera.updateProjectionMatrix()

    const scene = new THREE.Scene()
    scene.background = new THREE.Color(0xe0e0e0)

    const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth)

    const material = new THREE.MeshBasicMaterial({ color: 0xff4500 })  // greenish blue

    const cube = new THREE.Mesh(geometry, material)

    var edge = new THREE.EdgesGeometry(cube.geometry)
    var edgeMaterial = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 1 })
    var wireframe = new THREE.LineSegments(edge, edgeMaterial)

    scene.add(cube, wireframe)

    function animate() {
        requestAnimationFrame(animate)

        let rotationX = 0
        let rotationY = 0
        let rotationZ = 0

        if(angle == 'standard-0') {
            rotationX = 0.60
            rotationY = -0.80
            rotationZ = 0
        }
        
        if(angle == 'standard-90') {
            rotationX = 0.60
            rotationY = 0.80
            rotationZ = 0
        }
        
        if(angle == 'turn-up-0') {
            rotationX = -1.20
            rotationY = 0
            rotationZ = 0.80
        }
        
        if(angle == 'turn-up-90') {
            rotationX = -1.20
            rotationY = 0
            rotationZ = -0.80
        }

        if(angle == 'turn-side-0') {
            rotationX = 0.60
            rotationY = -0.60
            rotationZ = -1.60
        }
        
        if(angle == 'turn-side-90') {
            rotationX = 0.60
            rotationY = 0.60
            rotationZ = -1.60
        }

        cube.rotation.x = rotationX
        cube.rotation.y = rotationY
        cube.rotation.z = rotationZ
        
        wireframe.rotation.x = rotationX
        wireframe.rotation.y = rotationY
        wireframe.rotation.z = rotationZ

        renderer.render(scene, camera)
    }
  
    animate()
}

generate3DBox('.standard-0', 'standard-0', 50, 45, 65)
generate3DBox('.standard-90', 'standard-90', 50, 45, 65)
generate3DBox('.turn-up-0', 'turn-up-0', 50, 45, 65)
generate3DBox('.turn-up-90', 'turn-up-90', 50, 45, 65)
generate3DBox('.turn-side-0', 'turn-side-0', 50, 45, 65)
generate3DBox('.turn-side-90', 'turn-side-90', 50, 45, 65)

Output needed

1 Answers

You can use a simple formula to set the zoom of the camera accordingly to the size. Here I am using cameraZoom = 45 / maxSize, which adapts the zoom of the camera to the largest dimension of the objects. It sets the zoom because setting the Z does not seem to work . The value 45 that I chose is arbitrary, you can change it to suit your needs.

// import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r127/build/three.module.js'

function generate3DBox(selector, angle, boxDepth, boxWidth, boxHeight) {

  let allowedAngles = [
    'standard-0',
    'standard-90',
    'turn-up-0',
    'turn-up-90',
    'turn-side-0',
    'turn-side-90'
  ]

  if (allowedAngles.indexOf(angle) < 0) {
    console.log("Angle is incorrect")
    return false
  }

  const canvas = document.querySelector(selector)
  const renderer = new THREE.WebGLRenderer({
    canvas,
    antialias: true
  })

  const canvasWidth = canvas.getBoundingClientRect().width
  const canvasHeight = canvas.getBoundingClientRect().height
  const minSize = Math.min(...[boxDepth, boxWidth, boxHeight])
  const maxSize = Math.max(...[boxDepth, boxWidth, boxHeight])

  const aspect = canvasWidth / canvasHeight
  const fov = 75
  const near = 0.1
  const far = 1000

  let cameraZoom = 1
  let cameraPosition = {
    left: canvasWidth / -2,
    right: canvasWidth / 2,
    top: canvasHeight / 2,
    bottom: canvasHeight / -2
  }

  const camera = new THREE.OrthographicCamera(
    cameraPosition.left,
    cameraPosition.right,
    cameraPosition.top,
    cameraPosition.bottom,
    near,
    far
  )

  camera.position.z = maxSize + minSize
  // console.log(camera.position)
  cameraZoom = 45 / maxSize
  camera.zoom = cameraZoom

  camera.updateProjectionMatrix()

  const scene = new THREE.Scene()
  scene.background = new THREE.Color(0xe0e0e0)

  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth)

  const material = new THREE.MeshBasicMaterial({
    color: 0xff4500
  }) // greenish blue

  const cube = new THREE.Mesh(geometry, material)
  //console.log(cube.position.z -= 25)

  var edge = new THREE.EdgesGeometry(cube.geometry)
  var edgeMaterial = new THREE.LineBasicMaterial({
    color: 0xffffff,
    linewidth: 1
  })
  var wireframe = new THREE.LineSegments(edge, edgeMaterial)

  scene.add(cube, wireframe)

  function animate() {
    requestAnimationFrame(animate)

    let rotationX = 0
    let rotationY = 0
    let rotationZ = 0

    if (angle == 'standard-0') {
      rotationX = 0.60
      rotationY = -0.80
      rotationZ = 0
    }

    if (angle == 'standard-90') {
      rotationX = 0.60
      rotationY = 0.80
      rotationZ = 0
    }

    if (angle == 'turn-up-0') {
      rotationX = -1.20
      rotationY = 0
      rotationZ = 0.80
    }

    if (angle == 'turn-up-90') {
      rotationX = -1.20
      rotationY = 0
      rotationZ = -0.80
    }

    if (angle == 'turn-side-0') {
      rotationX = 0.60
      rotationY = -0.60
      rotationZ = -1.60
    }

    if (angle == 'turn-side-90') {
      rotationX = 0.60
      rotationY = 0.60
      rotationZ = -1.60
    }

    cube.rotation.x = rotationX
    cube.rotation.y = rotationY
    cube.rotation.z = rotationZ

    wireframe.rotation.x = rotationX
    wireframe.rotation.y = rotationY
    wireframe.rotation.z = rotationZ

    renderer.render(scene, camera)
  }

  animate()
}
generate3DBox('.standard-0', 'standard-0', 90, 45, 65)
generate3DBox('.standard-90', 'standard-90', 90, 45, 65)
generate3DBox('.turn-up-0', 'turn-up-0', 90, 45, 65)
generate3DBox('.turn-up-90', 'turn-up-90', 90, 45, 65)
generate3DBox('.turn-side-0', 'turn-side-0', 90, 45, 65)
generate3DBox('.turn-side-90', 'turn-side-90', 90, 45, 65)
.flex {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 100px;
  gap: 20px;
}

canvas {
  width: 65px;
  height: 76px;
  border: solid #313131 1px;
  background: url('https://www.pier2pier.com/loadcalc/images/Spinner-1s-64px-gray.gif');
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ThreeJS with Sonar</title>
  <link rel="icon" href="#">
  <link rel="stylesheet" href="css/style.css">
</head>

<body>
  <div class="flex">
    <canvas class="standard-0"></canvas>
    <canvas class="standard-90"></canvas>

    <canvas class="turn-up-0"></canvas>
    <canvas class="turn-up-90"></canvas>

    <canvas class="turn-side-0"></canvas>
    <canvas class="turn-side-90"></canvas>
  </div>
  <script src="https://threejsfundamentals.org/threejs/resources/threejs/r127/build/three.min.js"></script>
  <script type="module" src="js/main.js"></script>
</body>

</html>

The second snippet shows a solution if you want to have the same zoom for all boxes. It calculates the zoom on the largest dimension of the largest box instead of calculating a different zoom for each box.

// import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r127/build/three.module.js'

function generate3DBox(selector, angle, boxDepth, boxWidth, boxHeight, zoom) {

  let allowedAngles = [
    'standard-0',
    'standard-90',
    'turn-up-0',
    'turn-up-90',
    'turn-side-0',
    'turn-side-90'
  ]

  if (allowedAngles.indexOf(angle) < 0) {
    console.log("Angle is incorrect")
    return false
  }

  const canvas = document.querySelector(selector)
  const renderer = new THREE.WebGLRenderer({
    canvas,
    antialias: true
  })

  const canvasWidth = canvas.getBoundingClientRect().width
  const canvasHeight = canvas.getBoundingClientRect().height
  const minSize = Math.min(...[boxDepth, boxWidth, boxHeight])
  const maxSize = Math.max(...[boxDepth, boxWidth, boxHeight])

  const aspect = canvasWidth / canvasHeight
  const fov = 75
  const near = 0.1
  const far = 1000

  let cameraZoom = 1
  let cameraPosition = {
    left: canvasWidth / -2,
    right: canvasWidth / 2,
    top: canvasHeight / 2,
    bottom: canvasHeight / -2
  }

  const camera = new THREE.OrthographicCamera(
    cameraPosition.left,
    cameraPosition.right,
    cameraPosition.top,
    cameraPosition.bottom,
    near,
    far
  )

  camera.position.z = maxSize + minSize
  // console.log(camera.position)
  cameraZoom = zoom || (45 / maxSize)
  camera.zoom = cameraZoom

  camera.updateProjectionMatrix()

  const scene = new THREE.Scene()
  scene.background = new THREE.Color(0xe0e0e0)

  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth)

  const material = new THREE.MeshBasicMaterial({
    color: 0xff4500
  }) // greenish blue

  const cube = new THREE.Mesh(geometry, material)
  //console.log(cube.position.z -= 25)

  var edge = new THREE.EdgesGeometry(cube.geometry)
  var edgeMaterial = new THREE.LineBasicMaterial({
    color: 0xffffff,
    linewidth: 1
  })
  var wireframe = new THREE.LineSegments(edge, edgeMaterial)

  scene.add(cube, wireframe)

  function animate() {
    requestAnimationFrame(animate)

    let rotationX = 0
    let rotationY = 0
    let rotationZ = 0

    if (angle == 'standard-0') {
      rotationX = 0.60
      rotationY = -0.80
      rotationZ = 0
    }

    if (angle == 'standard-90') {
      rotationX = 0.60
      rotationY = 0.80
      rotationZ = 0
    }

    if (angle == 'turn-up-0') {
      rotationX = -1.20
      rotationY = 0
      rotationZ = 0.80
    }

    if (angle == 'turn-up-90') {
      rotationX = -1.20
      rotationY = 0
      rotationZ = -0.80
    }

    if (angle == 'turn-side-0') {
      rotationX = 0.60
      rotationY = -0.60
      rotationZ = -1.60
    }

    if (angle == 'turn-side-90') {
      rotationX = 0.60
      rotationY = 0.60
      rotationZ = -1.60
    }

    cube.rotation.x = rotationX
    cube.rotation.y = rotationY
    cube.rotation.z = rotationZ

    wireframe.rotation.x = rotationX
    wireframe.rotation.y = rotationY
    wireframe.rotation.z = rotationZ

    renderer.render(scene, camera)
  }

  animate()
}
var boxes = [['.standard-0', 'standard-0', 90, 45, 65],['.standard-90', 'standard-90', 90, 45, 65], ['.turn-up-0', 'turn-up-0', 90, 45, 65], ['.turn-up-90', 'turn-up-90', 90, 45, 65], ['.turn-side-0', 'turn-side-0', 90, 45, 65], ['.turn-side-90', 'turn-side-90', 130, 45, 65]];
var maxValue = Math.max(...boxes.map(([cls, angle, ...dimensions]) => Math.max(...dimensions)));
for (let i = 0; i < boxes.length; i++) {
    generate3DBox(...boxes[i], 45 / maxValue);
}
.flex {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 100px;
  gap: 20px;
}

canvas {
  width: 65px;
  height: 76px;
  border: solid #313131 1px;
  background: url('https://www.pier2pier.com/loadcalc/images/Spinner-1s-64px-gray.gif');
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ThreeJS with Sonar</title>
  <link rel="icon" href="#">
  <link rel="stylesheet" href="css/style.css">
</head>

<body>
  <div class="flex">
    <canvas class="standard-0"></canvas>
    <canvas class="standard-90"></canvas>

    <canvas class="turn-up-0"></canvas>
    <canvas class="turn-up-90"></canvas>

    <canvas class="turn-side-0"></canvas>
    <canvas class="turn-side-90"></canvas>
  </div>
  <script src="https://threejsfundamentals.org/threejs/resources/threejs/r127/build/three.min.js"></script>
  <script type="module" src="js/main.js"></script>
</body>

</html>

Related