Compile errors with ThreeJS examples in Typescript Development

Hello,

I have been following @seanwasere course on Three.js and Typescript. Recently there have been some changes to the way the development files are compiled to production. The development server still works as before - but I cannot compile the development files to bundle.js.

I’m receiving this error on multiple instances:
TS7034: Variable 'camera' implicitly has type 'any' in some locations where its type cannot be determined.

Some examples of errors I’m seeing in the client.ts.

    scene = new THREE.Scene();
    scene.**background** = new THREE.Color( 0xE6E6E6 );

(Property 'background' does not exist on type 'Object3D'.ts(2339)
document.getElementById( 'container' ).appendChild( renderer.domElement );

(Object is possibly 'null'.ts(2531))
function createMenu() {

    for ( const m in MOLECULES ) {

        const button = document.createElement( 'button' );

        button.innerHTML = m;

        **menu**.appendChild( button );

        const url = 'models/pdb/' + MOLECULES[ m ];

        button.addEventListener( 'click', generateButtonCallback( url ) );

    }

}

(const menu: HTMLElement | null)
(Object is possibly 'null'.)

I would really appreciate the help with this - as I have used this format of building a ThreeJS project as part of my university assignment hand-in.

Threejs has had many breaking changes in the last several versions, especially if you were using typescript, the older geometry base class and importing jsm libs directly.

Go back to the section
Begin Creating the Three.js Project - Three.js Tutorials (sbcode.net)
Do the 5 videos from there. It now uses the webpack bundler and I show you all the steps you need to get it working. It will only take you half an hour and it will make sense. The latest threejs works best when using a bundler.

Also, for problems concerning my course, its better to use the communications tools you get with the particular course suppliers platform. Use discourse.threejs.org for more generic threejs questions.

Also I don’t recognise the code that you pasted.

Thanks for the prompt reply,

The example template the course provides works perfectly fine. The problem I’m having is with trying to implement the ThreeJS examples. This one in particular ‘three.js examples’ - As I need to use the .pdb loader for the project I’m working on.

There used to be a section on the course which showed how to convert these examples. How can we achieve this now with the new ThreeJS changes?

I see,

I didn’t update that section.

So, I made it quickly using my boilerplate as the starting point

  1. Copy all the .pdb files to the ./dist/client/models/pdb folder
  2. Replace the ./dist/client/index.html with this below
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Three.js TypeScript Tutorials by Sean Bradley</title>
        <style>
            body {
                overflow: hidden;
                margin: 0px;
            }

            #menu {
                position: absolute;
                bottom: 20px;
                width: 100%;
                text-align: center;
                padding: 0;
                margin: 0;
            }

            button {
                color: rgb(255, 255, 255);
                background: transparent;
                border: 0px;
                padding: 5px 10px;
                cursor: pointer;
            }
            button:hover {
                background-color: rgba(0, 255, 255, 0.5);
            }
            button:active {
                color: #000000;
                background-color: rgba(0, 255, 255, 1);
            }

            .label {
                text-shadow: -1px 1px 1px rgb(0, 0, 0);
                margin-left: 25px;
                font-size: 20px;
            }
        </style>
    </head>

    <body>
        <div id="container"></div>
        <div id="menu"></div>
        <script type="module" src="bundle.js"></script>
    </body>
</html>
  1. Replace the ./src/client/client.ts with this below
// adapted from original js source : https://github.com/mrdoob/three.js/blob/44b8fa7b452dd0d291b9b930fdfc5721cb6ebee9/examples/webgl_loader_pdb.html
import * as THREE from 'three'

import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
import { PDBLoader } from 'three/examples/jsm/loaders/PDBLoader.js'
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'

let camera: THREE.PerspectiveCamera, scene: THREE.Scene, renderer: THREE.WebGLRenderer, labelRenderer: CSS2DRenderer
let controls: TrackballControls

let root: THREE.Group

const MOLECULES: { [key: string]: string } = {
    Ethanol: 'ethanol.pdb',
    Aspirin: 'aspirin.pdb',
    Caffeine: 'caffeine.pdb',
    Nicotine: 'nicotine.pdb',
    LSD: 'lsd.pdb',
    Cocaine: 'cocaine.pdb',
    Cholesterol: 'cholesterol.pdb',
    Lycopene: 'lycopene.pdb',
    Glucose: 'glucose.pdb',
    'Aluminium oxide': 'Al2O3.pdb',
    Cubane: 'cubane.pdb',
    Copper: 'cu.pdb',
    Fluorite: 'caf2.pdb',
    Salt: 'nacl.pdb',
    'YBCO superconductor': 'ybco.pdb',
    Buckyball: 'buckyball.pdb',
    Graphite: 'graphite.pdb',
}

const loader = new PDBLoader()
const offset = new THREE.Vector3()

const menu = document.getElementById('menu')

init()
animate()

function init() {
    scene = new THREE.Scene()
    scene.background = new THREE.Color(0x050505)

    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 5000)
    camera.position.z = 1000
    scene.add(camera)

    const light1 = new THREE.DirectionalLight(0xffffff, 0.8)
    light1.position.set(1, 1, 1)
    scene.add(light1)

    const light2 = new THREE.DirectionalLight(0xffffff, 0.5)
    light2.position.set(-1, -1, 1)
    scene.add(light2)

    root = new THREE.Group()
    scene.add(root)

    //

    renderer = new THREE.WebGLRenderer({ antialias: true })
    renderer.setPixelRatio(window.devicePixelRatio)
    renderer.setSize(window.innerWidth, window.innerHeight)
    ;(document.getElementById('container') as HTMLDivElement).appendChild(renderer.domElement)

    labelRenderer = new CSS2DRenderer()
    labelRenderer.setSize(window.innerWidth, window.innerHeight)
    labelRenderer.domElement.style.position = 'absolute'
    labelRenderer.domElement.style.top = '0px'
    labelRenderer.domElement.style.pointerEvents = 'none'
    ;(document.getElementById('container') as HTMLDivElement).appendChild(labelRenderer.domElement)

    //

    controls = new TrackballControls(camera, renderer.domElement)
    controls.minDistance = 500
    controls.maxDistance = 2000

    //

    loadMolecule('models/pdb/caffeine.pdb')
    createMenu()

    //

    window.addEventListener('resize', onWindowResize)
}

//

function generateButtonCallback(url: string) {
    return function () {
        loadMolecule(url)
    }
}

function createMenu() {
    for (const m in MOLECULES) {
        const button = document.createElement('button')
        button.innerHTML = m
        ;(menu as HTMLElement).appendChild(button)

        const url = 'models/pdb/' + MOLECULES[m]

        button.addEventListener('click', generateButtonCallback(url))
    }
}

//

function loadMolecule(url: string) {
    while (root.children.length > 0) {
        const object = root.children[0]
        ;(object.parent as THREE.Object3D).remove(object)
    }

    loader.load(url, function (pdb) {
        const geometryAtoms = pdb.geometryAtoms
        const geometryBonds = pdb.geometryBonds
        const json = pdb.json

        const boxGeometry = new THREE.BoxGeometry(1, 1, 1)
        const sphereGeometry = new THREE.IcosahedronGeometry(1, 3)

        geometryAtoms.computeBoundingBox()
        ;(geometryAtoms.boundingBox as THREE.Box3).getCenter(offset).negate()

        geometryAtoms.translate(offset.x, offset.y, offset.z)
        geometryBonds.translate(offset.x, offset.y, offset.z)

        let positions = geometryAtoms.getAttribute('position')
        const colors = geometryAtoms.getAttribute('color')

        const position = new THREE.Vector3()
        const color = new THREE.Color()

        for (let i = 0; i < positions.count; i++) {
            position.x = positions.getX(i)
            position.y = positions.getY(i)
            position.z = positions.getZ(i)

            color.r = colors.getX(i)
            color.g = colors.getY(i)
            color.b = colors.getZ(i)

            const material = new THREE.MeshPhongMaterial({ color: color })

            const object = new THREE.Mesh(sphereGeometry, material)
            object.position.copy(position)
            object.position.multiplyScalar(75)
            object.scale.multiplyScalar(25)
            root.add(object)

            const atom = json.atoms[i]

            const text = document.createElement('div')
            text.className = 'label'
            text.style.color = 'rgb(' + atom[3][0] + ',' + atom[3][1] + ',' + atom[3][2] + ')'
            text.textContent = atom[4]

            const label = new CSS2DObject(text)
            label.position.copy(object.position)
            root.add(label)
        }

        positions = geometryBonds.getAttribute('position')

        const start = new THREE.Vector3()
        const end = new THREE.Vector3()

        for (let i = 0; i < positions.count; i += 2) {
            start.x = positions.getX(i)
            start.y = positions.getY(i)
            start.z = positions.getZ(i)

            end.x = positions.getX(i + 1)
            end.y = positions.getY(i + 1)
            end.z = positions.getZ(i + 1)

            start.multiplyScalar(75)
            end.multiplyScalar(75)

            const object = new THREE.Mesh(
                boxGeometry,
                new THREE.MeshPhongMaterial({ color: 0xffffff })
            )
            object.position.copy(start)
            object.position.lerp(end, 0.5)
            object.scale.set(5, 5, start.distanceTo(end))
            object.lookAt(end)
            root.add(object)
        }

        render()
    })
}

//

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()

    renderer.setSize(window.innerWidth, window.innerHeight)
    labelRenderer.setSize(window.innerWidth, window.innerHeight)

    render()
}

function animate() {
    requestAnimationFrame(animate)
    controls.update()

    const time = Date.now() * 0.0004

    root.rotation.x = time
    root.rotation.y = time * 0.7

    render()
}

function render() {
    renderer.render(scene, camera)
    labelRenderer.render(scene, camera)
}

  1. npm run dev
  2. visit http://127.0.0.1:8080
  3. For building the production version of bundle.js
    see the section on Deploying to Production where you first Setup a Webpack Production Configuration

I used the latest version of the boilerplate Three.js TypeScript Boilerplate (github.com)

Works perfectly, thank you Sean.