2

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

Blues Clues
  • 1,694
  • 3
  • 31
  • 72
  • 1
    Possible duplicate: https://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object – WestLangley May 18 '21 at 11:24
  • @WestLangley how do you get the `dist` there? – Blues Clues May 19 '21 at 01:05
  • @WestLangley I've updated the fiddle code but still the same result. Please check https://jsfiddle.net/jonjieviduya/ehz62psd/5/ – Blues Clues May 19 '21 at 07:45
  • 1
    Since your geometry is long and is being rotated, consider computing the bounding sphere of your mesh's geometry and see https://stackoverflow.com/questions/22500214/calculate-camera-fov-distance-for-sphere. If you do not understand the concepts, you can get help at https://discourse.threejs.org. Someone else will have to help you debug your code. – WestLangley May 19 '21 at 11:11
  • What's the desired effect? For the camera to adjust itself to always have the object in view? It's not about fitting the object inside the canvas, it's about having the object inside the camera view. I would suggest reading a bit about how three js works here https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene – Sandsten May 26 '21 at 14:55
  • What is your desired output. If entire block will be seen is that enough or maybe you need smth else? – ulou May 27 '21 at 19:46
  • @ulou Please see my update. I've added an image of an example output that i need. – Blues Clues Jun 02 '21 at 07:57
  • Updated fiddle: https://jsfiddle.net/vkajwL4c/ Also, see https://stackoverflow.com/a/17567292/1461008 and https://stackoverflow.com/a/17518092/1461008 – WestLangley Jun 02 '21 at 11:18
  • @WestLangley When I put **100** for the `boxDepth`, it still overflowing – Blues Clues Jun 03 '21 at 02:05
  • Fiddle with bigger boxes. https://jsfiddle.net/2unabo9t/. Either change the camera zoom or increase the overall size of the camera frustum. – WestLangley Jun 03 '21 at 11:19
  • @WestLangley great, but the sizes are dynamic, so it can be changed anytime. How can we make the zoom dynamic depends on the object inside the canvas? I just tried to change the numbers to a bigger one, and it still overlaps outside the canvas – Blues Clues Jun 04 '21 at 01:33
  • @Jonjie can we use zoom instead of position ? – astroide Jun 06 '21 at 16:11
  • Compute the bounding sphere of the geometry and use the result to set the frustum parameters: left, right, top, and bottom. Leave zoom at 1. Update the camera projection matrix. – WestLangley Jun 06 '21 at 20:53

1 Answers1

0

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>
astroide
  • 807
  • 4
  • 16
  • This is good, but if you test theses values below, the zooms are not equal: generate3DBox('.standard-0', 'standard-0', 65, 5, 5) generate3DBox('.standard-90', 'standard-90', 65, 5, 5) generate3DBox('.turn-up-0', 'turn-up-0', 90, 45, 65) generate3DBox('.turn-up-90', 'turn-up-90', 90, 45, 605) generate3DBox('.turn-side-0', 'turn-side-0', 90, 45, 65) generate3DBox('.turn-side-90', 'turn-side-90', 0.10, 0.10, 0.10) – Blues Clues Jun 07 '21 at 01:06
  • @Jonjie you want the zoom to be equal for the six boxes ? – astroide Jun 07 '21 at 12:02
  • @Jonjie I added a snippet where all the boxes have the same zoom – astroide Jun 08 '21 at 00:22
  • No, your first answer is already good, but if you change the values like the values I've indicated above, some of the boxes takes up to 99% of the box. You can take a look at this example output: https://i.imgur.com/UwacMCB.png – Blues Clues Jun 08 '21 at 01:03