pmndrs / cannon-es

💣 A lightweight 3D physics engine written in JavaScript.
https://pmndrs.github.io/cannon-es/
MIT License
1.75k stars 130 forks source link

I cannot migrate a simple example from cannon to cannon-es #106

Closed 8Observer8 closed 2 years ago

8Observer8 commented 3 years ago

I have a very simple TypeScript example in cannon that just print a gravity:

import "cannon";

function main()
{
    const physicsWorld = new CANNON.World();
    physicsWorld.gravity.set(0, -9.82, 0);
    console.log("gravity = ", physicsWorld.gravity);
}

main();

You can run it on playground: https://plnkr.co/edit/EoxKFFYZgtXnTTEX?preview

Source code on GitHub with cannon: https://github.com/8Observer8/hello-cannon-ts Source code on GitHub with cannon-es: https://github.com/8Observer8/hello-cannon-es-ts

When I try to change "cannon" to "cannon-es", for example, this line:

import "cannon"

on this line:

import * as CANNON from "cannon-es";

I get this error: Uncaught ReferenceError: exports is not defined and this error: Uncaught TypeError: Cannot read property 'World' of undefined

image

marcofugaro commented 3 years ago

It's an issue with your bundler, please add cannon-es as a dependency and make sure it is included by browserify.

8Observer8 commented 3 years ago

I use AMD (Asynchronous Module Definition) and RequireJS instead of Browserify for this example. I added cannon-es as dependency. You can look it here. I made the same example like with cannon. cannon works but cannot does not work with the same settings. I added a link to the hello-cannon-es-ts in the topic.

requireConfig.ts

requirejs.config({
    baseUrl: "js",
    paths: {
        "cannon-es": "https://cdn.jsdelivr.net/npm/cannon-es@0.18.0/dist/cannon-es.cjs.min"
    }
});

requirejs(["main"], () => { });
marcofugaro commented 3 years ago

It does not work because that's a commonjs module. You need an umd module to make it work with RequireJS.

Cannon-es does not expose an umd module. Please use esmodules directly or alternatively use any bundler.

8Observer8 commented 3 years ago

Thank you very much. I will try later and I will write here about some result.

8Observer8 commented 2 years ago

It's sad that Cannon-es doesn't work like this:

<!DOCTYPE html>
<html lang="en">

<head>
    <script src="https://cdn.jsdelivr.net/npm/cannon-es@0.18.0/dist/cannon-es.js"></script>
</head>

<body>
    <script>
        const gravity = new CANNON.Vec3(0, -9.82, 0);
        console.log(gravity);
    </script>
</body>

</html>

I receive these errors:

Uncaught SyntaxError: Unexpected token 'export'
Uncaught ReferenceError: CANNON is not defined

But the original Cannon.js works very well:

<!DOCTYPE html>
<html lang="en">

<head>
    <script src="https://cdn.jsdelivr.net/npm/cannon@0.6.2/build/cannon.min.js"></script>
</head>

<body>
    <script>
        const gravity = new CANNON.Vec3(0, -9.82, 0);
        console.log(gravity);
    </script>
</body>

</html>
8Observer8 commented 2 years ago

I receive these errors:

Wow! I found how to solve these errors in this tutorial: https://sbcode.net/threejs/physics-cannonjs/ I just need to add type="module" as a script attribute:

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Example</title>
</head>

<body>
    <script type="module">
        import * as CANNON from "https://cdn.jsdelivr.net/npm/cannon-es@0.18.0/dist/cannon-es.js";

        const gravity = new CANNON.Vec3(0, -9.82, 0);
        console.log(gravity);
    </script>
</body>

</html>

Output:

Vec3 {x: 0, y: -9.82, z: 0}
8Observer8 commented 2 years ago

It's sad that Cannon-es doesn't work like this

Sorry, I see now that the Cannon-es examples https://github.com/pmndrs/cannon-es/tree/master/examples already work like this - they have the type="module" script attribute.

8Observer8 commented 2 years ago

It's an issue with your bundler, please add cannon-es as a dependency and make sure it is included by browserify.

Cannon-es works with Browserify and TypeScript without problems. I need to solve the problem with Require.js and AMD.

It does not work because that's a commonjs module. You need an umd module to make it work with RequireJS.

I will try to use UMD. When I try just replace "AMD" to "UMD" it says that ReferenceError: exports is not defined:

tsconfigs/tsconfig.debug.json

{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "module": "UMD",
        "sourceMap": true,
        "types": [
            "cannon-es",
            "requirejs"
        ]
    }
}

tsconfigs/tsconfig.json

{
    "compilerOptions": {
        "target": "ES5",
        "outDir": "../public/js"
    },
    "include": [
        "../src/client/**/*.ts"
    ]
}

tsconfigs/tsconfig.release.json

{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "module": "CommonJS",
        "sourceMap": false,
        "types": [
            "node"
        ]
    },
    "exclude": [
        "../src/client/requireConfig.ts"
    ]
}

src/client/requireConfig.ts

requirejs.config({
    baseUrl: "js",
    paths: {
        "cannon-es": "https://cdn.jsdelivr.net/npm/cannon-es@0.18.0/dist/cannon-es.cjs.min"
    }
});

requirejs(["main"], () => { });

src/client/main.ts

import * as CANNON from "cannon-es";
// import "cannon-es";

function main()
{
    const physicsWorld = new CANNON.World();
    physicsWorld.gravity.set(0, -9.82, 0);

    console.log("gravity = ", physicsWorld.gravity);
    const outputElement = document.getElementById("output");
    outputElement.innerHTML = "gravity = " + physicsWorld.gravity;
}

// Debug
main();

// Release
// window.onload = () => main();

public/index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Hello Cannon-ES. TypeScript</title>

    <!-- Debug -->
    <script data-main="js/requireConfig"
        src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
    <!-- Release -->
    <!-- <script src="js/bundle.min.js"></script> -->
</head>

<body>
    <div id="output"></div>
</body>

</html>

package.json

{
  "name": "hello-cannon-es-ts",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "debug": "tsc -p tsconfigs/tsconfig.debug.json",
    "compile": "tsc -p tsconfigs/tsconfig.release.json",
    "bundle": "browserify public/js/main.js -o public/js/bundle.js",
    "minify": "uglifyjs public/js/bundle.js -o public/js/bundle.min.js",
    "release": "npm run compile && npm run bundle && npm run minify"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cannon-es": "^0.18.0",
    "requirejs": "^2.3.6"
  },
  "devDependencies": {
    "@types/requirejs": "^2.1.32"
  }
}
marcofugaro commented 2 years ago

I just need to add type="module" as a script attribute

Yeah, that's how es modules work (cannon-es is an es module). Read more about it here

I need to solve the problem with Require.js and AMD.

My advice would be to stop using Require.js, it's an antiquated library and has been made obsolete with the introduction of es modules.

Closing since this is not related to cannon-es.

8Observer8 commented 2 years ago

Solution 1. Rollup

I copied the rollup.config.js file from the cannon-es folder: https://github.com/pmndrs/cannon-es/blob/master/rollup.config.js I installed rollup globally using the command: npm i -g rollup. I installed these packages in the root of my "Projects" directory: npm i -D @babel/core @babel/preset-env @babel/preset-typescript @rollup/plugin-babel @rollup/plugin-json @rollup/plugin-node-resolve @rollup/plugin-replace rollup-plugin-filesize. TypeScript was installed globally too.

My examples do not have include the node_modules folder and it saves a lot of space on my laptop because all those packages for rollup require 35 MB. For example I have this example Projects/my-example-cannonesjs-webgl-ts and this example has the package.json file with dependencies and so on, but it does not have the node_modules folder, it uses Projects/node_modules on the level above. I just copy names of dependencies to the package.json file to every example folder in the Projects folder.

I changed paths to my paths and I deleted what is unnecessary. Use the 'rollup -c' to create a bundle. I added sourcemap: true to output to be able to set breakpoints in VSCode.

rollup.config.js

import babel from '@rollup/plugin-babel'
import resolve from '@rollup/plugin-node-resolve'
import json from '@rollup/plugin-json'
// import replace from '@rollup/plugin-replace'
import filesize from 'rollup-plugin-filesize'

const extensions = ['.ts']

const babelOptions = {
  babelrc: false,
  extensions,
  exclude: '**/node_modules/**',
  babelHelpers: 'bundled',
  presets: [
    [
      '@babel/preset-env',
      {
        loose: true,
        modules: false,
        targets: '>1%, not dead, not ie 11, not op_mini all',
      },
    ],
    '@babel/preset-typescript',
  ],
}

export default [
  {
    input: `./src/client/main.ts`,
    output: { file: `public/js/bundle.js`, format: 'esm', sourcemap: true },
    plugins: [json(), resolve({ extensions }), babel(babelOptions), filesize()],
  },
  // {
  //   input: `./src/client/main.ts`,
  //   output: { file: `dist/cannon-es.cjs.js`, format: 'cjs' },
  //   plugins: [
  //     json(),
  //     resolve({ extensions }),
  //     babel(babelOptions),
  //     replace({
  //       // Use node built-in performance.now in commonjs environments
  //       'globalThis.performance': `require('perf_hooks') && require('perf_hooks').performance`,
  //     }),
  //     filesize(),
  //   ],
  // },
]
8Observer8 commented 2 years ago

Solution 2. Browserify and UglifyJS

  1. Install Browserify, UglifyJS, and TypeScript globally: npm i -g browserify uglify-js typescript
  2. Install tsify as debug package: npm i -D tsify
  3. Copy these commands to 'package.json':
    "scripts": {
    "debug": "browserify src/client/main.ts -p tsify --debug -o public/js/bundle.js",
    "bundle-release": "browserify src/client/main.ts -p tsify -o public/js/temp-bundle.js",
    "minify-release": "uglifyjs public/js/temp-bundle.js -o public/js/bundle.js",
    "release": "npm run bundle-release && npm run minify-release"
    }
  4. Add this line into 'body' in 'index.html': <script src="js/bundle.js"></script>
  5. Use this command to debug: npm run debug. You can set breakpoints in your browser in TypeScript files but I cannot set breakpoints in VSCode with the deprecated Chrome Extension from Microsoft
  6. Use this command to release: npm run release. Your application will be compressed to bundle.js
8Observer8 commented 2 years ago

But the main problem was not solved. My main goal is to place my TypeScript examples with cannon-es on some Playground like I made it with Cannon.js, Require.js, glMatrix and pure WebGL: https://plnkr.co/edit/ZrG1OFKjOyxsFbr3?preview

colored-objects-from-dae-and-cannonjs

Plunker and Require.js allow to run Jasmine Unit Tests in TypeScirpt: https://plnkr.co/edit/77Ex6gPTBxQyyXNML9tk?preview

Maybe someone knows how to run cannon-es with TypeScript on Plunker or on another playground with any bundler. Please, publish links on examples with cannon-es and TypeScript on some playgrounds.

8Observer8 commented 2 years ago

Maybe this information will help someone. Rollup works fine with TypeScript, Three.js, Cannon-es, and OrbitControls.

Debugging command: rollup -cmw where: -c - create a bundle, -m - debug mode, -w - watch)

Release commands:

I edited the rollup.config.js script from cannon-es (cannon-es is written in TypeScript): https://github.com/pmndrs/cannon-es/blob/master/rollup.config.js

rollup.config.js

import babel from "@rollup/plugin-babel";
import resolve from "@rollup/plugin-node-resolve";
import filesize from "rollup-plugin-filesize";

const extensions = [".ts"];

const babelOptions = {
    babelrc: false,
    extensions,
    exclude: "**/node_modules/**",
    babelHelpers: "bundled",
    presets: [
        [
            "@babel/preset-env",
            {
                loose: true,
                modules: false,
                targets: ">1%, not dead, not ie 11, not op_mini all",
            },
        ],
        "@babel/preset-typescript",
    ],
};

export default {
    input: "./src/main.ts",
    output: { file: "public/js/bundle.js" },
    plugins: [ resolve({ extensions }), babel(babelOptions), filesize() ]
}

src/main.ts

import * as THREE from "three";
import * as CANNON from "cannon-es";
import { ColladaLoader } from "../../../../node_modules/three/examples/jsm/loaders/ColladaLoader";
import { OrbitControls } from "../../../../node_modules/three/examples/jsm/controls/OrbitControls.js";

console.log(new THREE.Vector3(1, 2, 3));

const world = new CANNON.World( {gravity: new CANNON.Vec3(0, -9.8, 0)} );
console.log("gravity: ", world.gravity);

const loader = new ColladaLoader();
console.log(loader);

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderCanvas = document.getElementById("renderCanvas");
const renderer = new THREE.WebGLRenderer({ antialias: true, canvas: renderCanvas });
renderer.setSize(window.innerWidth, window.innerHeight);
const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.target = new THREE.Vector3(0, 0, 0);
console.log(orbitControls);

public/index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="renderCanvas"></canvas>

    <script src="js/bundle.js"></script>
</body>

</html>

image

image