├── .editorconfig ├── .eslintrc.cjs ├── .gitattributes ├── .github └── workflows │ └── actions.yml ├── .gitignore ├── .gitpod.yml ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── index.html ├── package-lock.json ├── package.json ├── packages ├── examples │ ├── .gitignore │ ├── assetpipeline.html │ ├── assetpipeline_obj_stage.html │ ├── build │ │ └── vite.config.BasicExampleOffscreenWorker.ts │ ├── index.html │ ├── main.css │ ├── models │ │ ├── obj │ │ │ └── main │ │ │ │ ├── cerberus │ │ │ │ └── Cerberus.obj │ │ │ │ ├── female02 │ │ │ │ ├── 01_-_Default1noCulling.jpg │ │ │ │ ├── 02_-_Default1noCulling.jpg │ │ │ │ ├── 03_-_Default1noCulling.jpg │ │ │ │ ├── female02.mtl │ │ │ │ ├── female02.obj │ │ │ │ ├── female02_vertex_colors.obj │ │ │ │ ├── readme.txt │ │ │ │ └── uv_grid_opengl.jpg │ │ │ │ ├── male02 │ │ │ │ ├── 01_-_Default1noCulling.jpg │ │ │ │ ├── male-02-1noCulling.jpg │ │ │ │ ├── male02.mtl │ │ │ │ ├── male02.obj │ │ │ │ ├── orig_02_-_Defaul1noCulling.jpg │ │ │ │ └── readme.txt │ │ │ │ ├── ninja │ │ │ │ └── ninjaHead_Low.obj │ │ │ │ ├── verify │ │ │ │ ├── gulpfile.js │ │ │ │ ├── verify.mtl │ │ │ │ └── verify.obj │ │ │ │ └── walt │ │ │ │ ├── WaltHead.mtl │ │ │ │ └── WaltHead.obj │ │ └── retrieveExtras.sh │ ├── obj2_basic.html │ ├── obj2_basic_offscreen.html │ ├── obj2_bugverify.html │ ├── obj2_obj_compare.html │ ├── obj2_options.html │ ├── obj2_react.html │ ├── obj2_react_mtl.html │ ├── obj2parallel_basic.html │ ├── package.json │ ├── scripts │ │ ├── buildWorker.mts │ │ ├── copyAssets.mts │ │ └── copyAssetsProduction.mts │ ├── src │ │ ├── examples │ │ │ ├── AssetPipelineLoaderExample.ts │ │ │ ├── ExampleCommons.ts │ │ │ ├── OBJLoader2BasicExample.ts │ │ │ ├── OBJLoader2BasicExampleOffscreen.ts │ │ │ ├── OBJLoader2BugVerify.ts │ │ │ ├── OBJLoader2OBJLoaderCompareExample.ts │ │ │ ├── OBJLoader2OptionsExample.ts │ │ │ ├── OBJLoader2ParallelBasicExample.ts │ │ │ ├── OBJLoader2Stage.ts │ │ │ ├── ReactExample.tsx │ │ │ ├── ReactExampleMtl.tsx │ │ │ └── ReactHelper.ts │ │ └── worker │ │ │ └── BasicExampleOffscreenWorker.ts │ ├── tsconfig.json │ ├── verify_assetpipeline.html │ ├── verify_assetpipeline_obj_stage.html │ ├── verify_obj2_basic.html │ ├── verify_obj2_basic_offscreen.html │ ├── verify_obj2_options.html │ ├── verify_obj2parallel_basic.html │ └── vite.config.production.ts └── objloader2 │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ ├── AssetPipelineLoader.ts │ ├── MtlObjBridge.ts │ ├── OBJLoader2.ts │ ├── OBJLoader2Parallel.ts │ ├── OBJLoader2Parser.ts │ ├── index.ts │ ├── utils │ │ └── ResourceDescriptor.ts │ └── worker │ │ └── OBJLoader2Worker.ts │ ├── tsconfig.json │ ├── vite.config.ts │ └── vite.config.worker.ts ├── scripts └── updateVersions.mts ├── tsconfig.build.json ├── tsconfig.json └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.json] 12 | indent_size = 2 13 | indent_style = space 14 | 15 | [*.html] 16 | indent_size = 4 17 | indent_style = tab 18 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | env: { 5 | node: true, 6 | browser: true, 7 | es2022: true 8 | }, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:@typescript-eslint/recommended' 12 | ], 13 | overrides: [ 14 | ], 15 | parserOptions: { 16 | ecmaVersion: 2022, 17 | sourceType: 'module' 18 | }, 19 | plugins: [ 20 | '@typescript-eslint' 21 | ], 22 | ignorePatterns: [ 23 | '**/{node_modules,dist,lib,out,bin}', 24 | '**/generated/**/*', 25 | '.eslintrc.js' 26 | ], 27 | rules: { 28 | // List of [ESLint rules](https://eslint.org/docs/rules/) 29 | 'arrow-parens': ['off', 'as-needed'], // do not force arrow function parentheses 30 | 'constructor-super': 'error', // checks the correct use of super() in sub-classes 31 | 'dot-notation': 'error', // obj.a instead of obj['a'] when possible 32 | 'eqeqeq': 'error', // ban '==', don't use 'smart' option! 33 | 'guard-for-in': 'error', // needs obj.hasOwnProperty(key) checks 34 | 'new-parens': 'error', // new Error() instead of new Error 35 | 'no-bitwise': 'error', // bitwise operators &, | can be confused with &&, || 36 | 'no-caller': 'error', // ECMAScript deprecated arguments.caller and arguments.callee 37 | 'no-cond-assign': 'error', // assignments if (a = '1') are error-prone 38 | 'no-debugger': 'error', // disallow debugger; statements 39 | 'no-eval': 'error', // eval is considered unsafe 40 | 'no-inner-declarations': 'off', // we need to have 'namespace' functions when using TS 'export =' 41 | 'no-labels': 'error', // GOTO is only used in BASIC ;) 42 | 'no-multiple-empty-lines': ['error', { 'max': 1 }], // two or more empty lines need to be fused to one 43 | 'no-new-wrappers': 'error', // there is no reason to wrap primitve values 44 | 'no-throw-literal': 'error', // only throw Error but no objects {} 45 | 'no-trailing-spaces': 'error', // trim end of lines 46 | 'no-unsafe-finally': 'error', // safe try/catch/finally behavior 47 | 'no-var': 'error', // use const and let instead of var 48 | 'space-before-function-paren': ['error', { // space in function decl: f() vs async () => {} 49 | 'anonymous': 'never', 50 | 'asyncArrow': 'always', 51 | 'named': 'never' 52 | }], 53 | 'semi': [2, 'always'], // Always use semicolons at end of statement 54 | 'quotes': [2, 'single', { 'avoidEscape': true }], // Prefer single quotes 55 | 'use-isnan': 'error', // isNaN(i) Number.isNaN(i) instead of i === NaN 56 | // List of [@typescript-eslint rules](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules) 57 | '@typescript-eslint/adjacent-overload-signatures': 'error', // grouping same method names 58 | '@typescript-eslint/array-type': ['error', { // string[] instead of Array 59 | 'default': 'array-simple' 60 | }], 61 | '@typescript-eslint/ban-types': 'error', // bans types like String in favor of string 62 | '@typescript-eslint/indent': 'error', // consistent indentation 63 | '@typescript-eslint/no-explicit-any': 'error', // don't use :any type 64 | '@typescript-eslint/no-misused-new': 'error', // no constructors for interfaces or new for classes 65 | '@typescript-eslint/no-namespace': 'off', // disallow the use of custom TypeScript modules and namespaces 66 | '@typescript-eslint/no-non-null-assertion': 'off', // allow ! operator 67 | '@typescript-eslint/no-unused-vars': ['error', { // disallow Unused Variables 68 | 'argsIgnorePattern': '^_' 69 | }], 70 | '@typescript-eslint/no-var-requires': 'error', // use import instead of require 71 | '@typescript-eslint/prefer-for-of': 'error', // prefer for-of loop over arrays 72 | '@typescript-eslint/prefer-namespace-keyword': 'error', // prefer namespace over module in TypeScript 73 | '@typescript-eslint/triple-slash-reference': 'error', // ban /// , prefer imports 74 | '@typescript-eslint/type-annotation-spacing': 'error' // consistent space around colon ':' 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | *.sh text eol=lf 4 | -------------------------------------------------------------------------------- /.github/workflows/actions.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | tags-ignore: 8 | - "v*.*.*" 9 | workflow_dispatch: 10 | inputs: 11 | ghp: 12 | description: 'Deploy to GitHub Page' 13 | required: true 14 | type: boolean 15 | default: false 16 | 17 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 18 | permissions: 19 | contents: read 20 | pages: write 21 | id-token: write 22 | 23 | jobs: 24 | build: 25 | name: objloader2 26 | runs-on: ubuntu-latest 27 | timeout-minutes: 15 28 | 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | - name: Use Node.js 33 | uses: actions/setup-node@v3 34 | with: 35 | node-version: '20' 36 | - name: Check Environment 37 | env: 38 | DEPLOY_GHP: ${{ false || github.event.inputs.ghp == 'true' || github.ref_name == 'main' }} 39 | run: | 40 | echo "DEPLOY_GHP=${DEPLOY_GHP}" >> $GITHUB_ENV 41 | - name: Install 42 | run: | 43 | npm ci 44 | - name: Lint 45 | run: | 46 | npm run lint 47 | - name: Build 48 | run: | 49 | npm run build 50 | - name: Test 51 | run: | 52 | npm run test 53 | - name: Build Production 54 | run: | 55 | npm --prefix packages/examples run build:production 56 | - name: Setup Pages 57 | if: ${{ env.DEPLOY_GHP == 'true' }} 58 | id: pages 59 | uses: actions/configure-pages@v4 60 | - name: Upload artifact 61 | if: ${{ env.DEPLOY_GHP == 'true' }} 62 | uses: actions/upload-pages-artifact@v3 63 | with: 64 | path: ./packages/examples/production/ 65 | 66 | deploy: 67 | needs: build 68 | environment: 69 | name: github-pages 70 | url: ${{ steps.deployment.outputs.page_url }} 71 | runs-on: ubuntu-latest 72 | steps: 73 | - name: Check Environment 74 | env: 75 | DEPLOY_GHP: ${{ false || github.event.inputs.ghp == 'true' || github.ref_name == 'main' }} 76 | run: | 77 | echo "DEPLOY_GHP=${DEPLOY_GHP}" >> $GITHUB_ENV 78 | - name: Deploy to GitHub Pages 79 | if: ${{ env.DEPLOY_GHP == 'true' }} 80 | id: deployment 81 | uses: actions/deploy-pages@v4 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | archive 2 | dist 3 | docs 4 | lib 5 | node_modules 6 | tsconfig.tsbuildinfo 7 | *.tgz 8 | .env 9 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-full 2 | 3 | ports: 4 | - port: 8080 5 | onOpen: open-browser 6 | 7 | tasks: 8 | - name: Terminal 9 | init: npm install && npm run build && npm run dev 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "dbaeumer.vscode-eslint", 5 | "davidanson.vscode-markdownlint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "version": "5.0.0", 4 | "configurations": [ 5 | { 6 | "type": "chrome", 7 | "request": "launch", 8 | "name": "Launch Chrome against localhost", 9 | "url": "http://localhost:8085", 10 | "webRoot": "${workspaceFolder}", 11 | "sourceMaps": true, 12 | "outFiles": [ 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.guides.bracketPairs": true, 3 | "editor.formatOnSave": false, 4 | "workbench.editor.revealIfOpen": true, 5 | "[javascript]": { 6 | "editor.formatOnSave": true 7 | }, 8 | "[typescript]": { 9 | "editor.formatOnSave": true 10 | }, 11 | "eslint.codeAction.showDocumentation": { 12 | "enable": true 13 | }, 14 | "eslint.format.enable": true, 15 | "eslint.validate": [ 16 | "javascript", 17 | "typescript" 18 | ], 19 | "[json]": { 20 | "editor.defaultFormatter": "vscode.json-language-features" 21 | }, 22 | "vitest.enable": true 23 | } 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 6.2.1 - 2024-04-19 4 | 5 | - Update three.js from v0.160.0 to v0.163.0 6 | - Update all devDependencies with exception of eslint 7 | 8 | ## 6.2.1 - 2024-01-05 9 | 10 | - Switch to `wtd-core@3.0.0` and `wtd-three-ext@3.0.0` 11 | - Adjust code to the changes 12 | - Example OBJLoader2 basic (Offscreen Canvas) now relies on utility functions supplied by `wtd-core` and `wtd-three-ext` 13 | 14 | ## 6.1.0 - 2023-10-21 15 | 16 | - Upgrade to versions `2.3.0` of `wtd-core` and `wtd-three-ext`. 17 | - `OBJLoader2Worker` can be re-executed. This was a regression, but also the code has been re-worked. 18 | 19 | ## 6.0.1 20 | 21 | - Avoid window reference if absolute url is provided [#77](https://github.com/kaisalmen/WWOBJLoader/pull/77) 22 | - Added [Offscreen canvas example](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_basic_offscreen.html) 23 | - Updated dependencies and aligned code with type definitions of three.js release 156. 24 | 25 | ## 6.0.0 26 | 27 | - Converted all code to TypeScript 28 | - Align vertex colors to linear (three.js) [#70](https://github.com/kaisalmen/WWOBJLoader/issues/70) 29 | - Re-added `AssetPipelineLoader`, `AssetPipeline`, `AssetTask` and `ResourceDescriptor` to the package 30 | - Updated dependencies 31 | 32 | ## 5.0.0 33 | 34 | - Clean-up and uncluttering 35 | - Remove all code related to worker online assembly and minification workarounds 36 | - Upgrade to versions `2.1.0` of `wtd-core` and `wtd-three-ext` 37 | - `OBJLoader2Parallel` is no longer using `WorkerTaskDirector`, but only using the generic `WorkerTask` 38 | - Transformed repository to npm workspaces 39 | - Switched to vite for development and bundle creation [#63](https://github.com/kaisalmen/WWOBJLoader/issues/63) 40 | - Webworker with three-wtm and webpack [#60](https://github.com/kaisalmen/WWOBJLoader/issues/60) 41 | - Make parser independent and thereby reduce worker size to a minimum 42 | - Fully rely on module workers. Use vite config to generate standard workers from module workers at build time 43 | - `package.json` now exports: 44 | - `wwobjloader2`: Unpacked javascript module code 45 | - `wwobjloader2/bundle`: An esm bundle 46 | - `wwobjloader2/worker`: The raw module parser worker 47 | - `bundle/worker/module`: Pre-packaged module parser worker 48 | - `bundle/worker/classic`: Pre-packaged classic parser worker 49 | - Add @react-three/fiber examples [#69](https://github.com/kaisalmen/WWOBJLoader/pull/69) 50 | - Explore aligning types with the core loaders [#68](https://github.com/kaisalmen/WWOBJLoader/issues/68) 51 | - three.js versioning in package file [#65](https://github.com/kaisalmen/WWOBJLoader/issues/65) 52 | 53 | ## 4.0.1 54 | 55 | - `three.js` is no longer a **peerDependency**. It is just a **dependency**. This resolves issue #58. 56 | - Updated **devDependencies** to resolve potential security issues. 57 | 58 | ## 4.0.0 59 | 60 | - All worker related functionality has been moved to external `WorkerTaskManager`, see [three-wtm](https://github.com/kaisalmen/three-wtm). 61 | - Introduction of potentially Infinite running `WorkerTaskManager` example `wtm_potentially_infinite.html` 62 | - Updated Example `webgl_loader_obj2_options.html`: Expose parser options and logging configuration in UI 63 | - Completely removed the need to use gulp. 64 | - Use snowpack for development. 65 | - Use rollup for library creation and used rollup and snowpack for supplement of the library verification environment. 66 | 67 | ## 3.2.0 68 | 69 | - Changed structure of the repository to mimic three.js layout 70 | - Fixes #18335 CodeSerializer works with uglified code 71 | - All multiple files loading examples now use `AssetPipelineLoader` prototype. 72 | 73 | ## 3.1.2 74 | 75 | - three.js issue 17769: 76 | - Move `ObjectManipulator` into `WorkerRunner` and export it from there 77 | - Remove `OBJLoader2Parser` import from `WorkerRunner`. Remove unneeded functions from DefaultWorkerPayloadHandler and WorkerRunner and aligned typescript definitions 78 | - The parser in DefaultWorkerPayloadHandler should not be limited to `OBJ2LoaderParser`. js and ts was not aligned here. 79 | 80 | ## 3.1.1 81 | 82 | - three.js issue 17615/17711: ObjLoader2Parser materials are not applied in worker 83 | - Additionally, allow material override from `OBJLoader2#addMaterials` to `MaterialHandler#addMaterials` 84 | 85 | ## 3.1.0 86 | 87 | - `OBJLoader2` is now based on `Loader` (three.js issue 17406) 88 | - Updated typescript definitions (fixed three.js issue 17364) Note: `OBJLoader2Parallel#parse` is implemented differently 89 | - Unified load and parse behaviour of `OBJLoader2` and `OBJLoader2Parallel`: Separate onParseComplete is gone. onLoad serves the same purpose in both sync an parallel use cases 90 | - Fix logging verbosity (MaterialHandler, etc) 91 | - Fixed three.js 15227 along 92 | 93 | ## 3.0.0 94 | 95 | - Transformed the whole code base to jsm and added typescript definitions (in scope of complete three.js code base transformation). 96 | - Removed `LoaderWorkerDirector`. This is going to be replaced with more generic approach likely driven by three.js 97 | - `OBJLoader2` extends `OBJLoader2Parser`: Moved common functions (parser configuration and callback) to `OBJLoader2Parser`. Here aim was to remove redundant function definitions in both the independent parser code and in `OBJLoader2`. 98 | - `OBJLoader2Parser`: All private functions are now identified by "_". Provides functions and members for extensions. 99 | - `WorkerExecutionSupport` is able to create the worker code for `OBJLoader2Parallel` by concatenation of existing code pieces (memory or files) or loading a jsm file that contains all dependencies. 100 | - `MaterialHandler` and `MeshReceiver` have been extracted from OBJLoader2. The are re-usable `WorkerExecutionSupport` context. 101 | - All MTL handling code has been removed from OBJLoader2. New approach via bridge `MtlObjBridge`. 102 | - OBJLoader2 will not get any further dependencies. `WorkerExecutionSupport` will use OBJLoader2 and other loaders. 103 | - OBJ Verify has been updated to use modules for both `OBJLoader` and `OBJLoader2`. 104 | - Started `AssetPipelineLoader` prototyping 105 | 106 | ## 2.5.1 107 | 108 | - three.js issue 15219: Materials are initialised as objects. 109 | - three.js issue 15468. `OBJLoader2`: Reduced log level from warn to info when defaultMaterial is used when material name was not resolvable. 110 | - three.js issue 16307: Backported onError function usage from OBJLoader2/Parser V3. Unified callback naming 111 | 112 | ## 2.5.0 113 | 114 | - Issue #47: Fixed incorrect vertex color pointerC initialization (omitting first set of values) 115 | - Pull Request #46: It is now possible to run `THREE.OBJLoader2` in nodejs 10.5.0+. Thanks to @Cobertos 116 | - Replaced Singletons with pure function/prototype definitions (backport from dev (V3.0.0)). Reason: Counter issues with worker code Blob generation from minified code base (e.g when using webpack) 117 | - three.js issue 12942: Align `setPath` and `setResourcePath` meaning and handling 118 | 119 | ## 2.4.2 120 | 121 | - Issue #43: `OBJLoader2` allows to register a generic error handler. If this callback is available it will be used instead of a throw. `LoaderWorkerDirector` uses this callback to report a problem, but continue with the next loading task. `loadMtl` now allows to pass onProgress and onError as well. 122 | 123 | ## 2.4.1 124 | 125 | - three.js issue 14010: `TRHEE.OBJLoader2.loadMtl` transforms an ArrayBuffer to String `THREE.LoaderUtils.decodeText` if content is provided as ArrayBuffer 126 | - three.js issue 14032: Vertex Color value was not correctly initialized. Vertex colors are now correctly used 127 | - Issue #40: Added function `TRHEE.OBJLoader2.setUseOAsMesh` to enforce mesh creation on occurrence of "o". The default is false (spec compliant). 128 | - Issue #39: Ensure name of `THREE.LoaderSupport.ResourceDescriptor` always has a default name 129 | - Issue #38: Fixed onMeshAlter and onLoadMaterials callback usage in `THREE.LoaderSupport.WorkerDirector` and fixed handling of returned objects in `THREE.LoaderSupport.MeshBuilder` 130 | 131 | ## 2.4.0 132 | 133 | - three.js issue 13197: 134 | - Added forceWorkerDataCopy to THREE.LoaderSupport.WorkerSupport and THREE.LoaderSupport.WorkerDirector 135 | - THREE.OBJLoader2 handles cached resources properly. This increases overall performance as no unnecessary reloads are requested. 136 | - THREE.OBJLoader2: Reduced Parser complexity: 137 | - Simplified slash counting used for face type detection 138 | - One buildFace function is used for all four face types, lines and points including indices (=vertex reduction) creation if wanted. 139 | - String processing (o, g, mtllib and usemtl) just concatenates chars 140 | - Overall speed improvements due simpler code paths 141 | - Removed THREE.LoaderSupport.ConsoleLogger: Added setLogging function as replacement where required. Console logging is behind boolean flags. **Important:** Code adjustments are required. 142 | - Removed THREE.LoaderSupport.LoaderBase: Many functions are coupled with OBJLoader2. It was simply not generic enough and added unneeded complexity. 143 | - Reduced THREE.LoaderSupport.PrepData to minimum set of generic functions. Simple properties are added by demand and are no longer enforced. 144 | - Renamed THREE.LoaderSupport.Builder to THREE.LoaderSupport.MeshBuilder 145 | - Added objverify to npm package 146 | - Updated documentation 147 | 148 | ## 2.3.1 149 | 150 | - Issue #10: Moved load and checkResourceDescriptorFiles from OBJLoader2 to LoaderBase. Re-use generic functions in other loaders 151 | 152 | ## 2.3.0 153 | 154 | - Issue #28: Parser Verification. Reduce overall complexity of Parser. It is now a single class. Missing 'g' statement don't cripple parsing. 155 | - Issue #31: Worker code still works when mangling is used during minification. 156 | - Issue #32: THREE.LoaderSupport.ConsoleLogger: Allow to pass additional arguments to error, warn, info and debug 157 | - Issue #33: Added support for Points and fixed Lines along 158 | 159 | ## 2.2.1 160 | 161 | - Issue #27: Multiple mesh definitions (vertices, normals, uvs and faces) within one group are now supported. Needed to remove early release of vertex data from memory. 162 | - Issue #28: Negative face indices are now supported. 163 | - Issue #29: Cleaned loadMtl API and clarified `WorkerSupport.run` contract. Transferable is automatically attached if data is an ArrayBuffer. 164 | 165 | ## 2.2.0 166 | 167 | - WorkerRunnerRefImpl changes: No longer required `THREE.LoaderSupport.Validator` and `THREE.LoaderSupport.ConsoleLogger` which reduces size of worker. Default workers only include WorkerRunnerRefImpl for establishing communication and no longer need the Validator or the ConsoleLogger. 168 | - LoaderWorkerSupport changes: It always ensures logConfig parameters are properly initialized before being passed to worker. Fixed logging problems. 169 | - Issue #25: OBJLoader2 now logs an error if `THREE.LoaderSupport` is not included as script in HTML. The same is true for missing `THRE.MTLLoader`, but only if method `loadMtl` is used. 170 | - Issue #26: `WorkerSuport` now contains a inner private class `LoaderWorker` that encapsulates the native worker. This separates the runtime functionality from the setup and interaction. Workers are now terminated when immediately when they are not running otherwise `LoaderWorker.terminateRequested` ensures termination when final execution status is reached. `WorkerDirector` now properly handles shutdown of workers. Evaluation of status is always performed in `WorkerDirector.processQueue`. `WorkerDirector.callbackOnFinishedProcessing` is called when processing is completed and all workers are terminated. This allows to clear all meshes, for example. 171 | 172 | ## 2.1.2 173 | 174 | - Added onLoadMaterials allowing alteration of materials when they have been loaded 175 | - Issue #21 Part2: Fixed new mesh detection (offset and not) solely relies on 'v' and 'f' occurrences. 'o' and 'g' are meta information, that no longer drive the decision 176 | - Issue #22: WorkerDirector now only allows implementations that have property callbacks 177 | - Example WWOBJLoader2Stage: Added validity checks to ZipTools 178 | - Fixed worker code generated from minified version is broken 179 | 180 | ## 2.1.1 181 | 182 | - Issue #21: Fixed 'o' or 'g' declaration lead to early cleanup of stored vertex data 183 | - WorkerSupport: Added worker payloads "imageData" and "error" 184 | 185 | ## 2.1.0 186 | 187 | - Issue #18: Builder enhancements: 188 | - Materials can be added to it as regular objects, as jsonified objects (e.g. from worker) and as clone instruction (map of property and value) 189 | - Separated mesh processing from material processing 190 | - Builder no longer defines default materials. This is no the responsibility of the user (loader) 191 | - WorkerSupport is now able to load arbitrary js files into Blob used to create the Worker (as exemplary shown and used in MeshSpray) 192 | 193 | ## 2.0.1 194 | 195 | - three.js issue #12324: Fix slashes in string pattern (e.g. usemtl) were replaced by spaces 196 | - Fixed line processing with uv 197 | 198 | ## 2.0.0 199 | 200 | - `OBJLoader2` and `WWOBJLoader2` have been fused. Worker based asynchronous execution of the loader is now handled by `parseAsync`, `load` with `useAsync` flag or `run` which is used for batch processing 201 | - All common functionality independent of OBJ parsing has been moved to package `THREE.LoaderSupport`. Thease are: 202 | - Builder 203 | - LoadedMeshUserOverride 204 | - WorkerSupport 205 | - WorkerRunnerRefImpl 206 | - WorkerDirector 207 | - PrepData 208 | - Commons 209 | - Callbacks 210 | - Validator 211 | - ConsoleLogger 212 | - `OBJLoader2.parse` method accepts arraybuffer or string as input. 213 | - Indexed rendering is supported. 214 | - Issue #15: `ConsoleLogger` now encapsulates all console logging. Logging can be fully deactivated or switched to debug mode 215 | - Issue #16: progress callbacks provide numerical values to indicate overall progress of download or parsing 216 | 217 | ## 1.4.1 218 | 219 | Loader related changes: 220 | 221 | - Issue #14: RawObject was not set with usemtl name, but with mtllib 222 | 223 | ## 1.4.0 224 | 225 | Loader related changes: 226 | 227 | - Issue #12, three.js issue #11804, #11871, PR #11928: Added n-gon support 228 | 229 | ## 1.3.1 230 | 231 | Loader related changes: 232 | 233 | - Issue #12, three.js issue #11804, #11871, PR #11928: Added n-gon support 234 | 235 | ## 1.3.0 236 | 237 | Loader related changes: 238 | 239 | - SmoothingGroups: activeSmoothingGroup is ensured to be a number (integer). "0" instead of "off" did not lead to detection of flat shading. 240 | - Issue 8: OBJLoader2 sets the mesh name properly. Mesh name is taken from group name (g) if exists or object name (o). Fixed cut-off names (o, g, mtllib, usemtl) 241 | - Issue 9: Fixed debug logging in MeshCreator related to Multi-Material creation. 242 | - three.js issue 11422: OBJLoader2 and WWOBJLoader2 are able to load vertexColors when defined as: v x y z r g b 243 | - Added download/progress feedback in all examples 244 | 245 | Example related changes: 246 | 247 | - Replaced Boolean with own Validator function 248 | 249 | ## 1.2.1 250 | 251 | Loader related changes: 252 | 253 | - Validator and its functions replace all Boolean calls. It is included in THREE.OBJLoader2. 254 | - Versions are now defined inside OBJLoader2 and WWOBJLoader2. 255 | - Static OBJLoader2._getValidator and OBJLoader2_buildWebWorkerCode are reached via prototype of OBJLoader2. Instance of OBJLoader2 is no longer created. 256 | - Requires three.js release 85 (now available) 257 | 258 | Example related changes: 259 | 260 | - webgl_loader_obj2_ww_parallels: Fixed "Run Queue" started new run before first was completed. 261 | 262 | ## 1.2.0 263 | 264 | Loader related changes: 265 | 266 | - THREE.OBJLoader2.WWOBJLoader2 267 | - Function `_receiveWorkerMessage` now uses a meshDescription that allows to override material or bufferGeometry or to completely disregard the mesh. `THREE.OBJLoader2.WWOBJLoader2.LoadedMeshUserOverride` was introduced for this. 268 | - Allow usage of multiple callbacks per callback type 269 | - `THREE.OBJLoader2.WWOBJLoader2.PrepDataArrayBuffer` and `THREE.OBJLoader2.WWOBJLoader2.PrepDataFile` require less mandatory parameters. Setters are introduced to handle optional things 270 | - THREE.OBJLoader2.WWOBJLoader2Director 271 | - Added per queue object callbacks 272 | - Global callbacks in `prepareWorkers` will be specified with new object `OBJLoader2.WWOBJLoader2.PrepDataCallbacks`. This object is also used in both PrepData objects for defining extra per model callbacks in addition to the global ones 273 | - Callbacks will be reset and reassigned for every run 274 | - All Examples 275 | - Improve code quality and logging: Replaced != or == with Boolean() or ! Boolean() where applicable 276 | - Improve logging and comments 277 | - Restored compatibility with three.js release 84 278 | 279 | ## 1.1.1 280 | 281 | - wwobjloader2 npm relase 1.1.0 did not set three.js dependency properly. That's why it was immediately succeeded by this version. 282 | 283 | Loader related changes: 284 | 285 | - Adjusted to removal of MultiMaterial in three.js release 85. Therefore not compatible with three.js < 0.85.0. 286 | 287 | ## 1.0.7 / 1.0.6 288 | 289 | Improvements since initial release. This was the first npm release and the first release for three.js. 290 | 291 | Loader related changes: 292 | 293 | - THREE.OBJLoader2 294 | - Removed need for making Parser public. OBJLoader2 has a build function for web worker code. 295 | - MeshCreator is now private to OBJLoader2 296 | - Removed underscores from functions of private classes 297 | - THREE.OBJLoader2.WWOBJLoader2 298 | - Added checks for Blob and URL.createObjectURL 299 | - Worker code build: Removed need to adjust constructor and some new Object calls 300 | - Allow to properly set CORS to MTLLoader via WWOBJLoader2 and WWOBJLoader2Director 301 | - Now allows to enable/disable mesh streaming 302 | - Adjusted naming of web worker classes 303 | - All Examples 304 | - Library headers now carry references to development repository 305 | 306 | Example related changes: 307 | 308 | - webgl_loader_obj 309 | - Added GridHelper 310 | - Resources to load are now defined outside example classes 311 | - webgl_loader_obj2_ww 312 | - Allow to clear all meshes in 313 | - Allows to load user OBJ/MTL files 314 | - Added GridHelper 315 | - Resources to load are now defined outside example classes 316 | - All Examples 317 | - Created one page examples and tuned naming 318 | - All examples now use dat.gui 319 | - Removed namespace "THREE.examples" 320 | - Fixed comment typos 321 | - Fixed some code formatting issues 322 | - Fixed tabs in examples 323 | 324 | ## 1.0.0 325 | 326 | Initial public release. 327 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt update && apt upgrade -y && apt install -y curl unzip 4 | 5 | ARG username=devbox 6 | RUN adduser ${username} && usermod -aG sudo ${username} 7 | 8 | RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - 9 | RUN apt install -y nodejs 10 | RUN curl https://get.volta.sh | bash 11 | 12 | RUN apt autoremove 13 | 14 | WORKDIR /home/devbox/workspace 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2022 Kai Salmen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OBJLoader2 & OBJLoader2Parallel for three.js 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/kaisalmen/WWOBJLoader/blob/dev/LICENSE) 4 | [![WWOBJLoader](https://github.com/kaisalmen/WWOBJLoader/actions/workflows/actions.yml/badge.svg)](https://github.com/kaisalmen/WWOBJLoader/actions/workflows/actions.yml) 5 | [![Github Pages](https://img.shields.io/badge/GitHub-Pages-blue?logo=github)](https://kaisalmen.github.io/WWOBJLoader) 6 | [![NPM Version](https://img.shields.io/npm/v/wwobjloader2.svg)](https://www.npmjs.com/package/wwobjloader2) 7 | [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/kaisalmen/WWOBJLoader) 8 | 9 | **OBJLoader2** is a loader for the **OBJ** file format. It is an alternative to [OBJLoader](https://github.com/mrdoob/three.js/blob/dev/examples/jsm/loaders/OBJLoader.js) included in [three.js](https://threejs.org). The loader and its parser can be used on Main via **OBJLoader2** or in parallel inside a web worker via **OBJLoader2Parallel**. 10 | 11 | ## Project History 12 | 13 | New versions of **OBJLoader2** and **OBJLoader2Parallel** are from now on again released as npm modules independent of three.js. The first stable version that was released independent again is 4.0.0. Versions 3.x.y were never released as independent npm and only in combination with three.ts itself. 14 | 15 | Between release of version 5.0.0 and 6.0.0 all code has been transformed to TypeScript. 16 | 17 | ### Changelog 18 | 19 | Interested in recent changes? Check the [CHANGELOG](https://github.com/kaisalmen/WWOBJLoader/blob/main/CHANGELOG.md). 20 | 21 | ## Development 22 | 23 | ### Getting Started 24 | 25 | There exist three possibilities: 26 | 27 | * Checkout the repository and run `npm install`, `npm run build` and then `npm run dev` to spin up local Vite dev server 28 | * Press the `Gitpod` button above and start coding and using the examples directly in the browser 29 | * Checkout the repository and use `docker-compose up -d` to spin up local Vite dev server. 30 | 31 | Whatever environment you choose to start [Vite](https://vitejs.dev/) is used to serve the code and the examples using it. With this setup you are able to change the code and examples without invoking an additional bundler. Vite ensures all imported npm modules are available if previously installed in local environment (see `npm install`). 32 | 33 | If you run Vite locally you require a **nodejs LTS** and **npm**. The Gitpod and local docker environment ensure all prerequisites are fulfilled. 34 | 35 | In any environment the server is reachable on port 8085. 36 | 37 | ### Examples 38 | 39 | If you want to get started see take a look at the following examples. They get more advanced from top to bottom: 40 | 41 | * OBJLoader2 basic: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_basic.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2BasicExample.ts)] 42 | * OBJLoader2 basic (Offscreen Canvas): [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_basic_offscreen.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2BasicExampleOffscreen.ts)] 43 | * OBJLoader2Parallel basic: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2parallel_basic.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2ParallelBasicExample.ts)] 44 | * OBJLoader2 usage options: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_options.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2OptionsExample.ts)] 45 | * OBJLoader2 / OBJLoader parser capability comparison: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_obj_compare.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2OBJLoaderCompareExample.ts)] 46 | * OBJLoader2 in react with a .jpg texture: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_react.html)] [[tsx](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/ReactExample.tsx)] 47 | * OBJLoader2 in react with a .mtl material: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_react-mtl.html)] [[tsx](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/ReactExampleMtl.tsx)] 48 | * AssetPipelineLoader basic example: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/assetpipeline.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/AssetPipelineLoaderExample.ts)] 49 | 50 | Try out all examples here: 51 | 52 | ### Main Branch 53 | 54 | Main development now takes place on branch [main](https://github.com/kaisalmen/WWOBJLoader/tree/main). Tags identify the releases. The stable branch has been retired. 55 | 56 | ## Feature Overview 57 | 58 | ### OBJLoader2Parser 59 | 60 | The parser `OBJLoader2Parser` used by `OBJLoader2` and `OBJLoader2Parallel` has all OBJ parsing capabilities of `OBJLoader` from three.js, plus some extra feature. Please see the following list: 61 | 62 | * The `parse` methods of `OBJLoader2Parser` accepts `ArrayBuffer` or `String` as input. Text processing is approx. 15-20 pecent slower. 63 | * In case `OBJLoader2Parallel` the of Parser `OBJLoader2Parser` is executed inside a worker. 64 | * `OBJLoader2Parser` features indexed rendering including vertex reduction. 65 | * Indexed rendering is available if switched on via `setUseIndices` (see `useIndices` in example **[OBJLoader2 usage options](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2OptionsExample.ts#L63)**). 66 | * Face N-Gons are supported. 67 | * Multi-Materials are created when needed. 68 | * Flat smoothing defined by "s 0" or "s off" is supported and Multi-Material is created when one object/group defines both smoothing groups equal and not equal to zero. 69 | * Support for points and lines is available since V2.3.0. 70 | * New mesh detection relies on 'g' occurrence or 'f', 'l' or 'p' type change (since V2.3.0). This allows multiple mesh definitions within one group. 71 | * Negative face indices are supported (issue #28) 72 | * The parser is now a single class that can be directly stored as string and therefore embedded in module or standard Workers (since V4.0.0). 73 | 74 | ## WorkerTaskDirector Core Library 75 | 76 | `WorkerTask` from [wtd-core](https://github.com/kaisalmen/wtd/tree/main/packages/wtd-core) is used to control everything regarding workers. This library was separated with the 4.0.0 release. It now evolves as independent library that is utilized by `OBJLoader2Parallel`. 77 | 78 | ## Docs 79 | 80 | Run `npm run doc` to create the documentation in [directory]((./packages/objloader2/docs). 81 | 82 | Happy coding! 83 | 84 | Kai 85 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | obj2dev: 5 | build: 6 | dockerfile: ./Dockerfile 7 | context: . 8 | ports: 9 | - target: 8085 10 | published: 8085 11 | protocol: tcp 12 | mode: host 13 | volumes: 14 | - ./:/home/devbox/workspace/:rw 15 | command: ["bash", "-c", "npm install && npm run build && npm run dev"] 16 | working_dir: /home/devbox/workspace 17 | container_name: obj2dev 18 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Examples 7 | 8 | 9 | 10 |
11 |

Examples

12 | 13 | Please execute npm run dev to launch the vite dev server.
14 | Please execute npm run verify to serve the static examples. 15 | 16 |

OBJLoader2 / OBJLoader2Parallel

17 | OBJLoader2 basic: [vite] [static] 18 |
19 | OBJLoader2 basic (Offscreen): [vite] [static] 20 |
21 | OBJLoader2Parallel basic: [vite] [static] 22 |
23 | OBJLoader2 usage options: [vite] [static] 24 |
25 | 26 |

AssetPipeline

27 | Asset Pipeline Test: [vite] [static] 28 |
29 | OBJLoader2 Stage (requires extra models): [vite] [static] 30 |
31 | 32 |

OBJLoader2 Implementation Verification

33 | OBJLoader2 / OBJLoader parser capability comparison: [vite] 34 |
35 | OBJLoader2 Bug Verification (requires private model files): [vite] 36 |
37 | 38 |

OBJLoader2 with @react-three/fiber

39 | OBJLoader2 in react with a .jpg texture: [vite] 40 |
41 | OBJLoader2 in react with a .mtl material: [vite] 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "objloader2-workspace", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "description": "OBJLoader2 & OBJLoader2Parallel for three.js", 6 | "author": { 7 | "name": "kaisalmen", 8 | "url": "https://www.kaisalmen.de" 9 | }, 10 | "private": "true", 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/kaisalmen/WWOBJLoader.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/kaisalmen/WWOBJLoader/issues" 18 | }, 19 | "scripts": { 20 | "clean": "npm run clean --workspaces", 21 | "lint": "eslint {**/src/**/*.ts,**/src/**/*.tsx,**/test/**/*.ts,**/test/**/*.tsx}", 22 | "lint:fix": "eslint eslint {**/src/**/*.ts,**/src/**/*.tsx,**/test/**/*.ts,**/test/**/*.tsx} --fix", 23 | "doc": "npm run doc --workspace packages/objloader2", 24 | "compile": "tsc --build tsconfig.build.json", 25 | "watch": "tsc --build tsconfig.build.json --watch", 26 | "build": "npm run build --workspaces", 27 | "build:obj2": "npm run build --workspace packages/objloader2", 28 | "dev": "vite", 29 | "dev:debug": "vite --debug --force", 30 | "verify": "npm run verify --workspace packages/examples", 31 | "release:prepare": "npm run reset:repo && npm ci && npm run lint && npm run build && npm run doc && shx cp -f README.md packages/objloader2/README.md", 32 | "test": "vitest", 33 | "update:versions:dev": "vite-node ./scripts/updateVersions.mts dev", 34 | "update:versions:real": "vite-node ./scripts/updateVersions.mts real", 35 | "update:reinstall": "vite-node ./scripts/updateVersions.mts reinstall", 36 | "reset:repo:dry": "git clean -f -d -x --dry-run --exclude=packages/examples/models --exclude=archive", 37 | "reset:repo": "git clean -f -d -x --exclude=packages/examples/models --exclude=archive" 38 | }, 39 | "keywords": [], 40 | "homepage": "https://github.com/kaisalmen/WWOBJLoader#README", 41 | "devDependencies": { 42 | "@typescript-eslint/eslint-plugin": "~7.7.0", 43 | "@types/node": "~20.12.7", 44 | "@types/shelljs": "~0.8.15", 45 | "editorconfig": "~2.0.0", 46 | "eslint": "~8.56.0", 47 | "shelljs": "~0.8.5", 48 | "shx": "~0.3.4", 49 | "typedoc": "~0.25.13", 50 | "typedoc-plugin-markdown": "~3.17.1", 51 | "typescript": "~5.4.5", 52 | "vite": "~5.2.9", 53 | "vitest": "~1.5.0" 54 | }, 55 | "volta": { 56 | "node": "20.12.2", 57 | "npm": "10.5.0" 58 | }, 59 | "workspaces": [ 60 | "packages/objloader2", 61 | "packages/examples" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /packages/examples/.gitignore: -------------------------------------------------------------------------------- 1 | src/worker/generated 2 | libs 3 | models/draco 4 | models/gltf 5 | models/obj/bugs 6 | models/obj/extra 7 | models/obj/misc 8 | production 9 | -------------------------------------------------------------------------------- /packages/examples/assetpipeline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | three.js webgl - AutoAssetLoader example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | AutoAssetLoader example 14 |
15 |
16 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/examples/assetpipeline_obj_stage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | three.js webgl - OBJLoader2 Stage 5 | 6 | 7 | 8 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 | OBJLoader2 Stage 30 |
31 |
32 | 33 | 34 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/examples/build/vite.config.BasicExampleOffscreenWorker.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { defineConfig } from 'vite'; 3 | 4 | const config = defineConfig({ 5 | build: { 6 | lib: { 7 | entry: path.resolve(__dirname, '../src/worker/BasicExampleOffscreenWorker.ts'), 8 | name: 'BasicExampleOffscreenWorker', 9 | fileName: (format) => `BasicExampleOffscreenWorker-${format}.js`, 10 | formats: ['es', 'iife'] 11 | }, 12 | outDir: 'src/worker/generated', 13 | emptyOutDir: false 14 | } 15 | }); 16 | 17 | export default config; 18 | -------------------------------------------------------------------------------- /packages/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Examples 7 | 8 | 9 | 10 |
11 |

Examples

12 | 13 |

OBJLoader2 / OBJLoader2Parallel

14 | OBJLoader2 basic 15 |
16 | OBJLoader2 basic (Offscreen) 17 |
18 | OBJLoader2Parallel basic 19 |
20 | OBJLoader2 usage options 21 |
22 | 23 |

AssetPipeline

24 | Asset Pipeline Test 25 | 26 |

OBJLoader2 Implementation Verification

27 | OBJLoader2 / OBJLoader parser capability comparison 28 | 29 |

OBJLoader2 with @react-three/fiber

30 | OBJLoader2 in react with a .jpg texture 31 |
32 | OBJLoader2 in react with a .mtl material 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/examples/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | background-color: #000; 4 | color: #fff; 5 | font-family: Monospace; 6 | font-size: 13px; 7 | line-height: 24px; 8 | } 9 | 10 | a { 11 | color: limegreen; 12 | text-decoration: none; 13 | } 14 | 15 | a:hover { 16 | text-decoration: underline; 17 | } 18 | 19 | button { 20 | cursor: pointer; 21 | text-transform: uppercase; 22 | } 23 | 24 | canvas { 25 | display: block; 26 | } 27 | 28 | #info { 29 | position: absolute; 30 | top: 0px; 31 | width: 100%; 32 | padding: 10px; 33 | box-sizing: border-box; 34 | text-align: center; 35 | -moz-user-select: none; 36 | -webkit-user-select: none; 37 | -ms-user-select: none; 38 | user-select: none; 39 | pointer-events: none; 40 | z-index: 1; /* TODO Solve this in HTML */ 41 | } 42 | 43 | b { 44 | color: #bbbbbb 45 | } 46 | 47 | h1, h2 { 48 | color: darkorange 49 | } 50 | 51 | a, button, input, select { 52 | pointer-events: auto; 53 | } 54 | 55 | .dg.ac { 56 | -moz-user-select: none; 57 | -webkit-user-select: none; 58 | -ms-user-select: none; 59 | user-select: none; 60 | z-index: 2 !important; /* TODO Solve this in HTML */ 61 | } 62 | -------------------------------------------------------------------------------- /packages/examples/models/obj/main/female02/01_-_Default1noCulling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaisalmen/WWOBJLoader/36ea1306d4c8cd2c0002bbfe7f8ee80eb399867b/packages/examples/models/obj/main/female02/01_-_Default1noCulling.jpg -------------------------------------------------------------------------------- /packages/examples/models/obj/main/female02/02_-_Default1noCulling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaisalmen/WWOBJLoader/36ea1306d4c8cd2c0002bbfe7f8ee80eb399867b/packages/examples/models/obj/main/female02/02_-_Default1noCulling.jpg -------------------------------------------------------------------------------- /packages/examples/models/obj/main/female02/03_-_Default1noCulling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaisalmen/WWOBJLoader/36ea1306d4c8cd2c0002bbfe7f8ee80eb399867b/packages/examples/models/obj/main/female02/03_-_Default1noCulling.jpg -------------------------------------------------------------------------------- /packages/examples/models/obj/main/female02/female02.mtl: -------------------------------------------------------------------------------- 1 | # Material Count: 6 2 | newmtl FrontColorNoCullingID__01_-_Default1noCulli 3 | Ns 154.901961 4 | Ka 0.000000 0.000000 0.000000 5 | Kd 0.800000 0.800000 0.800000 6 | Ks 0.165000 0.165000 0.165000 7 | Ni 1.000000 8 | d 1.000000 9 | illum 2 10 | map_Kd 01_-_Default1noCulling.jpg 11 | 12 | 13 | newmtl _02_-_Default1noCulli__02_-_Default1noCulli 14 | Ns 154.901961 15 | Ka 0.000000 0.000000 0.000000 16 | Kd 0.640000 0.640000 0.640000 17 | Ks 0.165000 0.165000 0.165000 18 | Ni 1.000000 19 | d 1.000000 20 | illum 2 21 | map_Kd 02_-_Default1noCulling.jpg 22 | 23 | 24 | newmtl _01_-_Default1noCulli__01_-_Default1noCulli 25 | Ns 154.901961 26 | Ka 0.000000 0.000000 0.000000 27 | Kd 0.640000 0.640000 0.640000 28 | Ks 0.165000 0.165000 0.165000 29 | Ni 1.000000 30 | d 1.000000 31 | illum 2 32 | map_Kd 01_-_Default1noCulling.jpg 33 | 34 | 35 | newmtl FrontColorNoCullingID__03_-_Default1noCulli 36 | Ns 154.901961 37 | Ka 0.000000 0.000000 0.000000 38 | Kd 0.800000 0.800000 0.800000 39 | Ks 0.165000 0.165000 0.165000 40 | Ni 1.000000 41 | d 1.000000 42 | illum 2 43 | map_Kd 03_-_Default1noCulling.jpg 44 | 45 | 46 | newmtl _03_-_Default1noCulli__03_-_Default1noCulli 47 | Ns 154.901961 48 | Ka 0.000000 0.000000 0.000000 49 | Kd 0.640000 0.640000 0.640000 50 | Ks 0.165000 0.165000 0.165000 51 | Ni 1.000000 52 | d 1.000000 53 | illum 2 54 | map_Kd 03_-_Default1noCulling.jpg 55 | 56 | 57 | newmtl FrontColorNoCullingID__02_-_Default1noCulli 58 | Ns 154.901961 59 | Ka 0.000000 0.000000 0.000000 60 | Kd 0.800000 0.800000 0.800000 61 | Ks 0.165000 0.165000 0.165000 62 | Ni 1.000000 63 | d 1.000000 64 | illum 2 65 | map_Kd 02_-_Default1noCulling.jpg 66 | 67 | 68 | -------------------------------------------------------------------------------- /packages/examples/models/obj/main/female02/readme.txt: -------------------------------------------------------------------------------- 1 | Model by Reallusion iClone from Google 3d Warehouse: 2 | 3 | http://sketchup.google.com/3dwarehouse/details?mid=2c6fd128fca34052adc5f5b98d513da1 -------------------------------------------------------------------------------- /packages/examples/models/obj/main/female02/uv_grid_opengl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaisalmen/WWOBJLoader/36ea1306d4c8cd2c0002bbfe7f8ee80eb399867b/packages/examples/models/obj/main/female02/uv_grid_opengl.jpg -------------------------------------------------------------------------------- /packages/examples/models/obj/main/male02/01_-_Default1noCulling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaisalmen/WWOBJLoader/36ea1306d4c8cd2c0002bbfe7f8ee80eb399867b/packages/examples/models/obj/main/male02/01_-_Default1noCulling.jpg -------------------------------------------------------------------------------- /packages/examples/models/obj/main/male02/male-02-1noCulling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaisalmen/WWOBJLoader/36ea1306d4c8cd2c0002bbfe7f8ee80eb399867b/packages/examples/models/obj/main/male02/male-02-1noCulling.jpg -------------------------------------------------------------------------------- /packages/examples/models/obj/main/male02/male02.mtl: -------------------------------------------------------------------------------- 1 | # Material Count: 5 2 | newmtl _01_-_Default1noCulli__01_-_Default1noCulli 3 | Ns 154.901961 4 | Ka 0.000000 0.000000 0.000000 5 | Kd 0.640000 0.640000 0.640000 6 | Ks 0.165000 0.165000 0.165000 7 | Ni 1.000000 8 | d 1.000000 9 | illum 2 10 | map_Kd 01_-_Default1noCulling.jpg 11 | 12 | 13 | newmtl FrontColorNoCullingID_male-02-1noCulling.JP 14 | Ns 154.901961 15 | Ka 0.000000 0.000000 0.000000 16 | Kd 0.800000 0.800000 0.800000 17 | Ks 0.165000 0.165000 0.165000 18 | Ni 1.000000 19 | d 1.000000 20 | illum 2 21 | map_Kd male-02-1noCulling.jpg 22 | 23 | 24 | newmtl male-02-1noCullingID_male-02-1noCulling.JP 25 | Ns 154.901961 26 | Ka 0.000000 0.000000 0.000000 27 | Kd 0.640000 0.640000 0.640000 28 | Ks 0.165000 0.165000 0.165000 29 | Ni 1.000000 30 | d 1.000000 31 | illum 2 32 | map_Kd male-02-1noCulling.jpg 33 | 34 | 35 | newmtl orig_02_-_Defaul1noCu_orig_02_-_Defaul1noCu 36 | Ns 154.901961 37 | Ka 0.000000 0.000000 0.000000 38 | Kd 0.640000 0.640000 0.640000 39 | Ks 0.165000 0.165000 0.165000 40 | Ni 1.000000 41 | d 1.000000 42 | illum 2 43 | map_Kd orig_02_-_Defaul1noCulling.jpg 44 | 45 | 46 | newmtl FrontColorNoCullingID_orig_02_-_Defaul1noCu 47 | Ns 154.901961 48 | Ka 0.000000 0.000000 0.000000 49 | Kd 0.800000 0.800000 0.800000 50 | Ks 0.165000 0.165000 0.165000 51 | Ni 1.000000 52 | d 1.000000 53 | illum 2 54 | map_Kd orig_02_-_Defaul1noCulling.jpg 55 | 56 | 57 | -------------------------------------------------------------------------------- /packages/examples/models/obj/main/male02/orig_02_-_Defaul1noCulling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaisalmen/WWOBJLoader/36ea1306d4c8cd2c0002bbfe7f8ee80eb399867b/packages/examples/models/obj/main/male02/orig_02_-_Defaul1noCulling.jpg -------------------------------------------------------------------------------- /packages/examples/models/obj/main/male02/readme.txt: -------------------------------------------------------------------------------- 1 | Model by Reallusion iClone from Google 3d Warehouse: 2 | 3 | https://3dwarehouse.sketchup.com/user.html?id=0122725873552223594220183 4 | -------------------------------------------------------------------------------- /packages/examples/models/obj/main/verify/gulpfile.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var fs = require('fs'); 5 | 6 | var obj_verify = { 7 | vertices: [], 8 | normals: [], 9 | uvs: [], 10 | facesV: [], 11 | facesVn: [], 12 | facesVt: [], 13 | materials: [] 14 | }; 15 | 16 | obj_verify.vertices.push([- 1, 1, 1]); 17 | obj_verify.vertices.push([- 1, - 1, 1]); 18 | obj_verify.vertices.push([1, - 1, 1]); 19 | obj_verify.vertices.push([1, 1, 1]); 20 | obj_verify.vertices.push([- 1, 1, - 1]); 21 | obj_verify.vertices.push([- 1, - 1, - 1]); 22 | obj_verify.vertices.push([1, - 1, - 1]); 23 | obj_verify.vertices.push([1, 1, - 1]); 24 | 25 | obj_verify.normals.push([0, 0, 1]); 26 | obj_verify.normals.push([0, 0, - 1]); 27 | obj_verify.normals.push([0, 1, 0]); 28 | obj_verify.normals.push([0, - 1, 0]); 29 | obj_verify.normals.push([1, 0, 0]); 30 | obj_verify.normals.push([- 1, 0, 0]); 31 | 32 | obj_verify.uvs.push([0, 1]); 33 | obj_verify.uvs.push([1, 1]); 34 | obj_verify.uvs.push([1, 0]); 35 | obj_verify.uvs.push([0, 0]); 36 | 37 | obj_verify.facesV.push([1, 2, 3, 4]); 38 | obj_verify.facesV.push([8, 7, 6, 5]); 39 | obj_verify.facesV.push([4, 3, 7, 8]); 40 | obj_verify.facesV.push([5, 1, 4, 8]); 41 | obj_verify.facesV.push([5, 6, 2, 1]); 42 | obj_verify.facesV.push([2, 6, 7, 3]); 43 | 44 | obj_verify.facesVn.push([1, 1, 1, 1]); 45 | obj_verify.facesVn.push([2, 2, 2, 2]); 46 | obj_verify.facesVn.push([5, 5, 5, 5]); 47 | obj_verify.facesVn.push([3, 3, 3, 3]); 48 | obj_verify.facesVn.push([6, 6, 6, 6]); 49 | obj_verify.facesVn.push([4, 4, 4, 4]); 50 | 51 | obj_verify.facesVt.push([1, 2, 3, 4]); 52 | obj_verify.facesVt.push([1, 2, 3, 4]); 53 | obj_verify.facesVt.push([1, 2, 3, 4]); 54 | obj_verify.facesVt.push([1, 2, 3, 4]); 55 | obj_verify.facesVt.push([1, 2, 3, 4]); 56 | obj_verify.facesVt.push([1, 2, 3, 4]); 57 | 58 | obj_verify.materials.push('usemtl red'); 59 | obj_verify.materials.push('usemtl blue'); 60 | obj_verify.materials.push('usemtl green'); 61 | obj_verify.materials.push('usemtl lightblue'); 62 | obj_verify.materials.push('usemtl orange'); 63 | obj_verify.materials.push('usemtl purple'); 64 | 65 | 66 | function vobjCreateVertices(factor, offsets) { 67 | var output = '\n'; 68 | for (var x, y, z, i = 0, v = obj_verify.vertices, length = v.length; i < length; i++) { 69 | x = v[i][0] * factor + offsets[0]; 70 | y = v[i][1] * factor + offsets[1]; 71 | z = v[i][2] * factor + offsets[2]; 72 | output += 'v ' + x + ' ' + y + ' ' + z + '\n'; 73 | } 74 | return output; 75 | } 76 | 77 | function vobjCreateUvs() { 78 | var output = '\n'; 79 | for (var x, y, i = 0, vn = obj_verify.uvs, length = vn.length; i < length; i++) { 80 | x = vn[i][0]; 81 | y = vn[i][1]; 82 | output += 'vt ' + x + ' ' + y + '\n'; 83 | } 84 | return output; 85 | } 86 | 87 | function vobjCreateNormals() { 88 | var output = '\n'; 89 | for (var x, y, z, i = 0, vn = obj_verify.normals, length = vn.length; i < length; i++) { 90 | x = vn[i][0]; 91 | y = vn[i][1]; 92 | z = vn[i][2]; 93 | output += 'vn ' + x + ' ' + y + ' ' + z + '\n'; 94 | } 95 | return output; 96 | } 97 | 98 | function vobjCreateCubeV(offsets, groups, usemtls) { 99 | var output = '\n'; 100 | if (groups === null || groups.length === 0) groups = [null, null, null, null, null, null]; 101 | if (usemtls === null || usemtls.length === 0) usemtls = [null, null, null, null, null, null]; 102 | 103 | for (var group, usemtl, f0, f1, f2, f3, i = 0, facesV = obj_verify.facesV, length = facesV.length; i < length; i++) { 104 | f0 = facesV[i][0] + offsets[0]; 105 | f1 = facesV[i][1] + offsets[0]; 106 | f2 = facesV[i][2] + offsets[0]; 107 | f3 = facesV[i][3] + offsets[0]; 108 | 109 | group = groups[i]; 110 | usemtl = usemtls[i]; 111 | if (group) output += 'g ' + group + '\n'; 112 | if (usemtl) output += 'usemtl ' + usemtl + '\n'; 113 | output += 'f ' + f0 + ' ' + f1 + ' ' + f2 + ' ' + f3 + '\n'; 114 | } 115 | return output; 116 | } 117 | 118 | function vobjCreateCubeVVn(offsets, groups, usemtls) { 119 | var output = '\n'; 120 | if (groups === null || groups.length === 0) groups = [null, null, null, null, null, null]; 121 | if (usemtls === null || usemtls.length === 0) usemtls = [null, null, null, null, null, null]; 122 | 123 | for (var group, usemtl, f0, f1, f2, f3, i = 0, facesV = obj_verify.facesV, facesVn = obj_verify.facesVn; i < 6; i++) { 124 | f0 = facesV[i][0] + offsets[0] + '//' + (facesVn[i][0] + offsets[1]); 125 | f1 = facesV[i][1] + offsets[0] + '//' + (facesVn[i][1] + offsets[1]); 126 | f2 = facesV[i][2] + offsets[0] + '//' + (facesVn[i][2] + offsets[1]); 127 | f3 = facesV[i][3] + offsets[0] + '//' + (facesVn[i][3] + offsets[1]); 128 | 129 | group = groups[i]; 130 | usemtl = usemtls[i]; 131 | if (group) output += 'g ' + group + '\n'; 132 | if (usemtl) output += 'usemtl ' + usemtl + '\n'; 133 | output += 'f ' + f0 + ' ' + f1 + ' ' + f2 + ' ' + f3 + '\n'; 134 | } 135 | return output; 136 | } 137 | 138 | function vobjCreateCubeVVt(offsets, groups, usemtls) { 139 | var output = '\n'; 140 | if (groups === null || groups.length === 0) groups = [null, null, null, null, null, null]; 141 | if (usemtls === null || usemtls.length === 0) usemtls = [null, null, null, null, null, null]; 142 | 143 | for (var group, usemtl, f0, f1, f2, f3, i = 0, facesV = obj_verify.facesV, facesVt = obj_verify.facesVt; i < 6; i++) { 144 | f0 = facesV[i][0] + offsets[0] + '/' + (facesVt[i][0] + offsets[1]); 145 | f1 = facesV[i][1] + offsets[0] + '/' + (facesVt[i][1] + offsets[1]); 146 | f2 = facesV[i][2] + offsets[0] + '/' + (facesVt[i][2] + offsets[1]); 147 | f3 = facesV[i][3] + offsets[0] + '/' + (facesVt[i][3] + offsets[1]); 148 | 149 | group = groups[i]; 150 | usemtl = usemtls[i]; 151 | if (group) output += 'g ' + group + '\n'; 152 | if (usemtl) output += 'usemtl ' + usemtl + '\n'; 153 | output += 'f ' + f0 + ' ' + f1 + ' ' + f2 + ' ' + f3 + '\n'; 154 | } 155 | return output; 156 | } 157 | 158 | function vobjCreateCubeVVnVt(offsets, groups, usemtls) { 159 | var output = '\n'; 160 | if (groups === null || groups.length === 0) groups = [null, null, null, null, null, null]; 161 | if (usemtls === null || usemtls.length === 0) usemtls = [null, null, null, null, null, null]; 162 | 163 | for (var group, usemtl, f0, f1, f2, f3, i = 0, facesV = obj_verify.facesV, facesVt = obj_verify.facesVt, facesVn = obj_verify.facesVn; i < 6; i++) { 164 | f0 = facesV[i][0] + offsets[0] + '/' + (facesVt[i][0] + offsets[1]) + '/' + (facesVn[i][0] + offsets[2]); 165 | f1 = facesV[i][1] + offsets[0] + '/' + (facesVt[i][1] + offsets[1]) + '/' + (facesVn[i][1] + offsets[2]); 166 | f2 = facesV[i][2] + offsets[0] + '/' + (facesVt[i][2] + offsets[1]) + '/' + (facesVn[i][2] + offsets[2]); 167 | f3 = facesV[i][3] + offsets[0] + '/' + (facesVt[i][3] + offsets[1]) + '/' + (facesVn[i][3] + offsets[2]); 168 | 169 | group = groups[i]; 170 | usemtl = usemtls[i]; 171 | if (group) output += 'g ' + group + '\n'; 172 | if (usemtl) output += 'usemtl ' + usemtl + '\n'; 173 | output += 'f ' + f0 + ' ' + f1 + ' ' + f2 + ' ' + f3 + '\n'; 174 | } 175 | return output; 176 | } 177 | 178 | function build() { 179 | gutil.log('Building: verify.obj'); 180 | var offsets = [0, 0, 0]; 181 | var pos = [- 150, 50, 0]; 182 | fs.writeFileSync('./verify.obj', '# Verification OBJ created with gulp\n\n'); 183 | fs.appendFileSync('./verify.obj', 'mtllib verify.mtl\n\n# Cube no materials. Translated x:' + pos[0]); 184 | fs.appendFileSync('./verify.obj', vobjCreateVertices(10, pos)); 185 | fs.appendFileSync('./verify.obj', vobjCreateCubeV(offsets, null, null)); 186 | 187 | pos[0] += 50; 188 | offsets[0] += 8; 189 | fs.appendFileSync('./verify.obj', '\n\n# Cube with two materials. Translated x:' + pos[0]); 190 | fs.appendFileSync('./verify.obj', vobjCreateVertices(10, pos)); 191 | fs.appendFileSync('./verify.obj', vobjCreateCubeV(offsets, null, ['orange', null, null, 'purple', null, null])); 192 | 193 | pos[0] += 50; 194 | offsets[0] += 8; 195 | fs.appendFileSync('./verify.obj', '\n\n# Cube with normals no materials. Translated x:' + pos[0]); 196 | fs.appendFileSync('./verify.obj', vobjCreateVertices(10, pos)); 197 | fs.appendFileSync('./verify.obj', vobjCreateNormals()); 198 | fs.appendFileSync('./verify.obj', vobjCreateCubeVVn(offsets, ['cube3', null, null, null, null, null], ['lightblue', null, null, null, null, null])); 199 | 200 | pos[0] += 50; 201 | offsets[0] += 8; 202 | fs.appendFileSync('./verify.obj', '\n\n# Cube with uvs and red material. Translated x:' + pos[0]); 203 | fs.appendFileSync('./verify.obj', vobjCreateVertices(10, pos)); 204 | fs.appendFileSync('./verify.obj', vobjCreateUvs()); 205 | fs.appendFileSync('./verify.obj', vobjCreateCubeVVt(offsets, null, ['red', null, null, null, null, null])); 206 | 207 | pos[0] += 50; 208 | offsets[0] += 8; 209 | fs.appendFileSync('./verify.obj', '\n\n# cube with uvs and normals and material. Translated x' + pos[0]); 210 | fs.appendFileSync('./verify.obj', vobjCreateVertices(10, pos)); 211 | 212 | fs.appendFileSync('./verify.obj', vobjCreateCubeVVnVt(offsets, [], ['red', null, null, 'blue', null, 'green'])); 213 | 214 | pos[0] += 50; 215 | offsets[0] += 8; 216 | offsets[1] += 6; 217 | fs.appendFileSync('./verify.obj', '\n\n# cube with uvs and normals and two materials and group for every quad. Translated x:' + pos[0]); 218 | fs.appendFileSync('./verify.obj', vobjCreateVertices(10, pos)); 219 | fs.appendFileSync('./verify.obj', vobjCreateNormals()); 220 | fs.appendFileSync('./verify.obj', vobjCreateCubeVVnVt([- 9, offsets[1], offsets[2]], 221 | ['cube6a', 'cube6b', 'cube6c', 'cube6d', 'cube6e', 'cube6f'], 222 | ['green', null, null, 'orange', null, null])); 223 | 224 | pos[0] += 50; 225 | offsets[0] += 8; 226 | fs.appendFileSync('./verify.obj', '\n\n# cube with uvs and normals and six materials and six groups, one for every quad. Translated x:' + pos[0]); 227 | fs.appendFileSync('./verify.obj', vobjCreateVertices(10, pos)); 228 | fs.appendFileSync('./verify.obj', vobjCreateCubeVVnVt(offsets, 229 | ['cube6a', 'cube6b', 'cube6c', 'cube6d', 'cube6e', 'cube6f'], 230 | ['red', 'blue', 'green', 'lightblue', 'orange', 'purple'])); 231 | 232 | pos[0] += 50; 233 | offsets[0] += 8; 234 | fs.appendFileSync('./verify.obj', '\n\n# Point. Translated x:' + pos[0]); 235 | fs.appendFileSync('./verify.obj', vobjCreateVertices(10, pos)); 236 | fs.appendFileSync('./verify.obj', '\np -8 -7 -6 -5 -4 -3 -2 -1\n'); 237 | 238 | pos[0] += 50; 239 | offsets[0] += 8; 240 | fs.appendFileSync('./verify.obj', '\n\n# Line. Translated x:' + pos[0]); 241 | fs.appendFileSync('./verify.obj', vobjCreateVertices(10, pos)); 242 | fs.appendFileSync('./verify.obj', 'l -8 -7 -6 -5 -4 -3 -2 -1 -8 -5 -8 -4 -7 -6 -7 -3 -5 -1 -3 -2 -6 -2 -4 -1\n'); 243 | 244 | pos[0] += 50; 245 | offsets[0] += 8; 246 | fs.appendFileSync('./verify.obj', '\n\n# Line UV. Translated x:' + pos[0]); 247 | fs.appendFileSync('./verify.obj', vobjCreateVertices(10, pos)); 248 | fs.appendFileSync('./verify.obj', '\nl -8/-2 -7/-1 -6/-2 -5/-1\n'); 249 | } 250 | -------------------------------------------------------------------------------- /packages/examples/models/obj/main/verify/verify.mtl: -------------------------------------------------------------------------------- 1 | newmtl red 2 | Ka 1.000000 0.000000 0.000000 3 | Kd 1.000000 0.000000 0.000000 4 | Ks 0.000000 0.000000 0.000000 5 | illum 2 6 | Ns 0.000000 7 | map_Kd ../female02/02_-_Default1noCulling.jpg 8 | 9 | newmtl blue 10 | Ka 0.000000 0.000000 1.000000 11 | Kd 0.000000 0.000000 1.000000 12 | Ks 0.000000 0.000000 0.000000 13 | illum 1 14 | Ns 0.000000 15 | 16 | newmtl green 17 | Ka 0.000000 1.000000 0.000000 18 | Kd 0.000000 1.000000 0.000000 19 | Ks 0.000000 0.000000 0.000000 20 | illum 1 21 | Ns 0.000000 22 | 23 | newmtl lightblue 24 | Ka 0.000000 1.000000 1.000000 25 | Kd 0.000000 1.000000 1.000000 26 | Ks 0.000000 0.000000 0.000000 27 | illum 1 28 | Ns 0.000000 29 | 30 | newmtl orange 31 | Ka 1.000000 0.647059 0.000000 32 | Kd 1.000000 0.647059 0.000000 33 | Ks 0.000000 0.000000 0.000000 34 | illum 1 35 | Ns 0.000000 36 | 37 | newmtl purple 38 | Ka 0.825806 0.000000 0.825806 39 | Kd 0.825806 0.000000 0.825806 40 | Ks 0.000000 0.000000 0.000000 41 | illum 1 42 | Ns 0.000000 43 | -------------------------------------------------------------------------------- /packages/examples/models/obj/main/verify/verify.obj: -------------------------------------------------------------------------------- 1 | # Verification OBJ created with gulp 2 | 3 | mtllib verify.mtl 4 | 5 | # Cube no materials. Translated x:-150 6 | o cube 1 7 | v -160 60 10 8 | v -160 40 10 9 | v -140 40 10 10 | v -140 60 10 11 | v -160 60 -10 12 | v -160 40 -10 13 | v -140 40 -10 14 | v -140 60 -10 15 | 16 | f 1 2 3 4 17 | f 8 7 6 5 18 | f 4 3 7 8 19 | f 5 1 4 8 20 | f 5 6 2 1 21 | f 2 6 7 3 22 | 23 | 24 | # Cube with two materials. Translated x:-100 25 | o cube 2 26 | v -110 60 10 27 | v -110 40 10 28 | v -90 40 10 29 | v -90 60 10 30 | v -110 60 -10 31 | v -110 40 -10 32 | v -90 40 -10 33 | v -90 60 -10 34 | 35 | usemtl orange 36 | f 9 10 11 12 37 | f 16 15 14 13 38 | f 12 11 15 16 39 | usemtl purple 40 | f 13 9 12 16 41 | f 13 14 10 9 42 | f 10 14 15 11 43 | 44 | 45 | # Cube with normals no materials. Translated x:-50 46 | v -60 60 10 47 | v -60 40 10 48 | v -40 40 10 49 | v -40 60 10 50 | v -60 60 -10 51 | v -60 40 -10 52 | v -40 40 -10 53 | v -40 60 -10 54 | 55 | vn 0 0 1 56 | vn 0 0 -1 57 | vn 0 1 0 58 | vn 0 -1 0 59 | vn 1 0 0 60 | vn -1 0 0 61 | 62 | g cube3 63 | usemtl lightblue 64 | f 17//1 18//1 19//1 20//1 65 | f 24//2 23//2 22//2 21//2 66 | f 20//5 19//5 23//5 24//5 67 | f 21//3 17//3 20//3 24//3 68 | f 21//6 22//6 18//6 17//6 69 | f 18//4 22//4 23//4 19//4 70 | 71 | 72 | # Cube with uvs and red material. Translated x:0 73 | v -10 60 10 74 | v -10 40 10 75 | v 10 40 10 76 | v 10 60 10 77 | v -10 60 -10 78 | v -10 40 -10 79 | v 10 40 -10 80 | v 10 60 -10 81 | 82 | vt 0 1 83 | vt 1 1 84 | vt 1 0 85 | vt 0 0 86 | 87 | usemtl red 88 | f 25/1 26/2 27/3 28/4 89 | f 32/1 31/2 30/3 29/4 90 | f 28/1 27/2 31/3 32/4 91 | f 29/1 25/2 28/3 32/4 92 | f 29/1 30/2 26/3 25/4 93 | f 26/1 30/2 31/3 27/4 94 | 95 | 96 | # cube with uvs and normals and material. Translated x50 97 | v 40 60 10 98 | v 40 40 10 99 | v 60 40 10 100 | v 60 60 10 101 | v 40 60 -10 102 | v 40 40 -10 103 | v 60 40 -10 104 | v 60 60 -10 105 | 106 | usemtl red 107 | f 33/1/1 34/2/1 35/3/1 36/4/1 108 | f 40/1/2 39/2/2 38/3/2 37/4/2 109 | f 36/1/5 35/2/5 39/3/5 40/4/5 110 | usemtl blue 111 | f 37/1/3 33/2/3 36/3/3 40/4/3 112 | f 37/1/6 38/2/6 34/3/6 33/4/6 113 | usemtl green 114 | f 34/1/4 38/2/4 39/3/4 35/4/4 115 | 116 | 117 | # cube with uvs and normals and two materials and group for every quad. Translated x:100 118 | v 90 60 10 119 | v 90 40 10 120 | v 110 40 10 121 | v 110 60 10 122 | v 90 60 -10 123 | v 90 40 -10 124 | v 110 40 -10 125 | v 110 60 -10 126 | 127 | vn 0 0 1 128 | vn 0 0 -1 129 | vn 0 1 0 130 | vn 0 -1 0 131 | vn 1 0 0 132 | vn -1 0 0 133 | 134 | g cube6a 135 | usemtl green 136 | f -8/7/1 -7/8/1 -6/9/1 -5/10/1 137 | g cube6b 138 | f -1/7/2 -2/8/2 -3/9/2 -4/10/2 139 | g cube6c 140 | f -5/7/5 -6/8/5 -2/9/5 -1/10/5 141 | g cube6d 142 | usemtl orange 143 | f -4/7/3 -8/8/3 -5/9/3 -1/10/3 144 | g cube6e 145 | f -4/7/6 -3/8/6 -7/9/6 -8/10/6 146 | g cube6f 147 | f -7/7/4 -3/8/4 -2/9/4 -6/10/4 148 | 149 | 150 | # cube with uvs and normals and six materials and six groups, one for every quad. Translated x:150 151 | v 140 60 10 152 | v 140 40 10 153 | v 160 40 10 154 | v 160 60 10 155 | v 140 60 -10 156 | v 140 40 -10 157 | v 160 40 -10 158 | v 160 60 -10 159 | 160 | g cube6a 161 | usemtl red 162 | f 49/7/1 50/8/1 51/9/1 52/10/1 163 | g cube6b 164 | usemtl blue 165 | f 56/7/2 55/8/2 54/9/2 53/10/2 166 | g cube6c 167 | usemtl green 168 | f 52/7/5 51/8/5 55/9/5 56/10/5 169 | g cube6d 170 | usemtl lightblue 171 | f 53/7/3 49/8/3 52/9/3 56/10/3 172 | g cube6e 173 | usemtl orange 174 | f 53/7/6 54/8/6 50/9/6 49/10/6 175 | g cube6f 176 | usemtl purple 177 | f 50/7/4 54/8/4 55/9/4 51/10/4 178 | 179 | 180 | # Point. Translated x:200 181 | v 190 60 10 182 | v 190 40 10 183 | v 210 40 10 184 | v 210 60 10 185 | v 190 60 -10 186 | v 190 40 -10 187 | v 210 40 -10 188 | v 210 60 -10 189 | 190 | p -8 -7 -6 -5 -4 -3 -2 -1 191 | 192 | 193 | # Point/Line. Translated x:250 194 | v 240 60 10 195 | v 240 40 10 196 | v 260 40 10 197 | v 260 60 10 198 | v 240 60 -10 199 | v 240 40 -10 200 | v 260 40 -10 201 | v 260 60 -10 202 | l -8 -7 -6 -5 -4 -3 -2 -1 -8 -5 -8 -4 -7 -6 -7 -3 -5 -1 -3 -2 -6 -2 -4 -1 203 | 204 | 205 | # Line UV. Translated x:300 206 | v 290 60 10 207 | v 290 40 10 208 | v 310 40 10 209 | v 310 60 10 210 | v 290 60 -10 211 | v 290 40 -10 212 | v 310 40 -10 213 | v 310 60 -10 214 | 215 | l -8/-2 -7/-1 -6/-2 -5/-1 -------------------------------------------------------------------------------- /packages/examples/models/obj/main/walt/WaltHead.mtl: -------------------------------------------------------------------------------- 1 | # Material Count: 1 2 | newmtl lambert2SG.001 3 | Ns 92.156863 4 | Ka 0.000000 0.000000 0.000000 5 | Kd 0.640000 0.640000 0.640000 6 | Ks 0.250000 0.250000 0.250000 7 | Ni 1.000000 8 | d 1.000000 9 | illum 2 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/examples/models/retrieveExtras.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR_ME=$(realpath $(dirname ${0})) 4 | 5 | if [[ ! -d ${DIR_ME}/obj/extra/PTV1 ]]; then 6 | mkdir ${DIR_ME}/obj/extra/PTV1 7 | fi 8 | 9 | if [[ ! -d ${DIR_ME}/obj/extra/zomax ]]; then 10 | mkdir ${DIR_ME}/obj/extra/zomax 11 | fi 12 | 13 | echo -e "\nDownloading & decompressing PTV1.zip" 14 | curl https://kaisalmen.de/resource/obj/PTV1/PTV1.zip -o ${DIR_ME}/obj/extra/PTV1/PTV1.zip 15 | unzip -o ${DIR_ME}/obj/extra/PTV1/PTV1.zip -d ${DIR_ME}/obj/extra/PTV1/ 16 | 17 | echo -e "\nDownloading & decompressing zomax-net_haze-sink-scene.zip" 18 | curl https://kaisalmen.de/resource/obj/zomax/zomax-net_haze-sink-scene.zip -o ${DIR_ME}/obj/extra/zomax/zomax-net_haze-sink-scene.zip 19 | unzip -o ${DIR_ME}/obj/extra/zomax/zomax-net_haze-sink-scene.zip -d ${DIR_ME}/obj/extra/zomax/ 20 | 21 | echo -e "\nDownloading & decompressing zomax-net_haze-oven-scene.zip" 22 | curl https://kaisalmen.de/resource/obj/zomax/zomax-net_haze-oven-scene.zip -o ${DIR_ME}/obj/extra/zomax/zomax-net_haze-oven-scene.zip 23 | unzip -o ${DIR_ME}/obj/extra/zomax/zomax-net_haze-oven-scene.zip -d ${DIR_ME}/obj/extra/zomax/ 24 | -------------------------------------------------------------------------------- /packages/examples/obj2_basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2 basic 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | OBJLoader2 basic 14 |
15 |
16 | 17 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /packages/examples/obj2_basic_offscreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2 basic (Offscreen Canvas) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | OBJLoader2 basic (Offscreen Canvas) 14 |
15 |
16 | 17 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/examples/obj2_bugverify.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | js webgl - OBJLoader2 Bug Verification 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | OBJLoader2 Bug Verification 14 |
15 |
16 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/examples/obj2_obj_compare.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2 / OBJLoader parser capability comparison 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | OBJLoader2/OBJLoader verification 14 |
15 |
16 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/examples/obj2_options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2 usage options 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | OBJLoader2 usage options 17 |
18 |
19 | 20 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/examples/obj2_react.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2 React usage 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/examples/obj2_react_mtl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2 React usage 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/examples/obj2parallel_basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2Parallel Worker Module Support 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | OBJLoader2Parallel Worker Module Support 14 |
15 |
16 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "objloader2-examples", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": "true", 6 | "scripts": { 7 | "clean": "shx rm -rf *.tsbuildinfo ./dist ./libs", 8 | "compile": "tsc", 9 | "build:worker": "vite-node ./scripts/buildWorker.mts", 10 | "build": "npm run clean && npm run compile && npm run build:worker && npm run script:copy:assets", 11 | "clean:production": "shx rm -fr production", 12 | "build:production": "npm run build && npm run build:production:vite", 13 | "build:production:vite": "npm run clean:production && vite --config vite.config.production.ts build && npm run script:copy:assets:production", 14 | "script:copy:assets": "vite-node ./scripts/copyAssets.mts", 15 | "script:copy:assets:production": "vite-node ./scripts/copyAssetsProduction.mts", 16 | "serve": "http-server --port 20001 ./production", 17 | "verify": "http-server --port 20001 ./" 18 | }, 19 | "volta": { 20 | "node": "20.12.2", 21 | "npm": "10.5.0" 22 | }, 23 | "dependencies": { 24 | "@react-three/fiber": "~8.16.2", 25 | "wtd-core": "~3.0.0", 26 | "wwobjloader2": "~6.2.1", 27 | "lil-gui": "~0.19.1", 28 | "react": "~18.2.0", 29 | "react-dom": "~18.2.0", 30 | "three": "~0.163.0" 31 | }, 32 | "devDependencies": { 33 | "@types/react": "~18.2.47", 34 | "@types/react-dom": "~18.2.18", 35 | "@types/three": "~0.163.0", 36 | "http-server": "~14.1.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/examples/scripts/buildWorker.mts: -------------------------------------------------------------------------------- 1 | import shell from 'shelljs'; 2 | 3 | shell.rm('-f', './src/worker/generated/BasicExampleOffscreenWorker*.js'); 4 | shell.exec('vite -c build/vite.config.BasicExampleOffscreenWorker.ts build'); 5 | -------------------------------------------------------------------------------- /packages/examples/scripts/copyAssets.mts: -------------------------------------------------------------------------------- 1 | import shell from 'shelljs'; 2 | 3 | shell.mkdir('-p', './libs/worker'); 4 | shell.cp('-f', '../../node_modules/three/build/three.module.js', './libs/three.module.js'); 5 | shell.cp('-f', '../../node_modules/three/examples/jsm/controls/TrackballControls.js', './libs/TrackballControls.js'); 6 | shell.cp('-f', '../../node_modules/three/examples/jsm/loaders/MTLLoader.js', './libs/MTLLoader.js'); 7 | shell.cp('-f', '../../node_modules/three/examples/jsm/helpers/VertexNormalsHelper.js', './libs/VertexNormalsHelper.js'); 8 | shell.cp('-f', '../../node_modules/lil-gui/dist/lil-gui.esm.js', './libs/lil-gui.esm.js'); 9 | 10 | shell.mkdir('-p', './libs/wtd-core/offscreen'); 11 | shell.cp('-f', '../../node_modules/wtd-core/dist/*.js', './libs/wtd-core'); 12 | shell.cp('-f', '../../node_modules/wtd-core/dist/offscreen/*.js', './libs/wtd-core/offscreen'); 13 | shell.cp('-f', '../objloader2/lib/objloader2.js', './libs/objloader2.js'); 14 | shell.cp('-f', '../objloader2/lib/worker/OBJLoader2WorkerModule.js', './libs/worker/OBJLoader2WorkerModule.js'); 15 | shell.cp('-f', '../objloader2/lib/worker/OBJLoader2WorkerClassic.js', './libs/worker/OBJLoader2WorkerClassic.js'); 16 | -------------------------------------------------------------------------------- /packages/examples/scripts/copyAssetsProduction.mts: -------------------------------------------------------------------------------- 1 | import shell from 'shelljs'; 2 | 3 | shell.mkdir('-p', './production/models/obj/main/cerberus'); 4 | shell.cp('-f', './models/obj/main/cerberus/*', './production/models/obj/main/cerberus/'); 5 | 6 | shell.mkdir('-p', './production/models/obj/main/female02'); 7 | shell.cp('-f', './models/obj/main/female02/*', './production/models/obj/main/female02/'); 8 | 9 | shell.mkdir('-p', './production/models/obj/main/male02'); 10 | shell.cp('-f', './models/obj/main/male02/*', './production/models/obj/main/male02/'); 11 | 12 | shell.mkdir('-p', './production/models/obj/main/ninja'); 13 | shell.cp('-f', './models/obj/main/ninja/*', './production/models/obj/main/ninja/'); 14 | 15 | shell.mkdir('-p', './production/models/obj/main/verify'); 16 | shell.cp('-f', './models/obj/main/verify/*', './production/models/obj/main/verify/'); 17 | 18 | shell.mkdir('-p', './production/models/obj/main/walt'); 19 | shell.cp('-f', './models/obj/main/walt/*', './production/models/obj/main/walt/'); 20 | 21 | shell.mkdir('-p', './production/assets/worker'); 22 | shell.cp('-f', './src/worker/generated/*.js', './production/assets/worker'); 23 | shell.cp('-f', '../objloader2/lib/worker/OBJLoader2Worker*.js', './production/assets/worker'); 24 | -------------------------------------------------------------------------------- /packages/examples/src/examples/AssetPipelineLoaderExample.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from 'three'; 2 | import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; 3 | 4 | import { OBJLoader2, MtlObjBridge } from 'wwobjloader2'; 5 | import { ResourceDescriptor } from 'wwobjloader2'; 6 | import { AssetPipelineLoader, AssetPipeline, AssetTask } from 'wwobjloader2'; 7 | import { createThreeDefaultSetup, ExampleDefinition, renderDefault, ThreeDefaultSetup } from './ExampleCommons.js'; 8 | 9 | export class AssetPipelineLoaderExample implements ExampleDefinition { 10 | 11 | private setup: ThreeDefaultSetup; 12 | 13 | constructor(canvas: HTMLCanvasElement | null) { 14 | const cameraDefaults = { 15 | posCamera: new Vector3(0.0, 175.0, 500.0), 16 | posCameraTarget: new Vector3(0, 0, 0), 17 | near: 0.1, 18 | far: 10000, 19 | fov: 45 20 | }; 21 | this.setup = createThreeDefaultSetup(canvas, cameraDefaults, { 22 | width: canvas?.offsetWidth ?? 0, 23 | height: canvas?.offsetHeight ?? 0, 24 | pixelRatio: window.devicePixelRatio 25 | }); 26 | } 27 | 28 | getSetup() { 29 | return this.setup; 30 | } 31 | 32 | render() { 33 | renderDefault(this.setup); 34 | } 35 | 36 | run() { 37 | const assetTask0 = new AssetTask('task0'); 38 | const rdMtl = new ResourceDescriptor('./models/obj/main/female02/female02.mtl').setNeedStringOutput(true); 39 | assetTask0.setResourceDescriptor(rdMtl); 40 | const loaderConfigurationMtl = { 41 | resourcePath: './models/obj/main/female02/', 42 | materialOptions: {} 43 | }; 44 | assetTask0.setLoader(new MTLLoader(), loaderConfigurationMtl); 45 | 46 | const assetTask1 = new AssetTask('task1'); 47 | assetTask1.setLinker(new MtlObjBridge()); 48 | 49 | const assetTask2 = new AssetTask('task2'); 50 | const rdObj = new ResourceDescriptor('./models/obj/main/female02/female02.obj'); 51 | assetTask2.setResourceDescriptor(rdObj); 52 | assetTask2.setLoader(new OBJLoader2()); 53 | 54 | const assetPipeline = new AssetPipeline(); 55 | assetPipeline.addAssetTask(assetTask0); 56 | assetPipeline.addAssetTask(assetTask1); 57 | assetPipeline.addAssetTask(assetTask2); 58 | 59 | const assetPipelineLoader = new AssetPipelineLoader('testAssetPipelineLoader', assetPipeline); 60 | assetPipelineLoader.setBaseObject3d(this.setup.scene); 61 | assetPipelineLoader.run(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /packages/examples/src/examples/ExampleCommons.ts: -------------------------------------------------------------------------------- 1 | import { AmbientLight, DirectionalLight, GridHelper, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from 'three'; 2 | import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'; 3 | import { OffscreenPayload } from 'wtd-core'; 4 | 5 | export type CameraDefaults = { 6 | posCamera: Vector3; 7 | posCameraTarget: Vector3; 8 | near: number; 9 | far: number; 10 | fov: number; 11 | }; 12 | 13 | export type CanvasDimensions = { 14 | width: number; 15 | height: number; 16 | pixelRatio: number; 17 | }; 18 | 19 | export type ExampleDefinition = { 20 | render: () => void; 21 | run: () => void; 22 | getSetup(): ThreeDefaultSetup; 23 | } 24 | 25 | export type ProgressEventType = { 26 | detail: { 27 | text: string; 28 | } 29 | } 30 | 31 | export const reportProgress = (event: ProgressEventType) => { 32 | console.log(`Progress: ${event.detail.text}`); 33 | if (!isWorker()) { 34 | document.getElementById('feedback')!.innerHTML = event.detail.text; 35 | } 36 | }; 37 | 38 | export const isWorker = () => { 39 | return typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; 40 | }; 41 | 42 | export const executeExample = (app: ExampleDefinition) => { 43 | console.log('Starting initialisation phase...'); 44 | if (!isWorker()) { 45 | window.addEventListener('resize', () => resizeDisplayGL(app.getSetup()), false); 46 | } 47 | resizeDisplayGL(app.getSetup()); 48 | 49 | const requestRender = () => { 50 | requestAnimationFrame(requestRender); 51 | app.render(); 52 | }; 53 | requestRender(); 54 | 55 | app.run(); 56 | }; 57 | 58 | export type ThreeDefaultSetup = { 59 | renderer: WebGLRenderer; 60 | canvas: HTMLCanvasElement; 61 | canvasDimensions: CanvasDimensions; 62 | scene: Scene; 63 | camera: PerspectiveCamera; 64 | cameraTarget: Vector3; 65 | cameraDefaults: CameraDefaults; 66 | controls?: TrackballControls; 67 | } 68 | 69 | export const createThreeDefaultSetup = (canvas: HTMLCanvasElement | null, cameraDefaults: CameraDefaults, 70 | canvasDimensions: CanvasDimensions, forceControls?: boolean): ThreeDefaultSetup => { 71 | if (canvas === null) { 72 | throw Error('Bad element HTML given as canvas.'); 73 | } 74 | 75 | const setup = {} as ThreeDefaultSetup; 76 | setup.canvas = canvas; 77 | setup.renderer = new WebGLRenderer({ 78 | canvas: setup.canvas, 79 | antialias: true 80 | }); 81 | setup.renderer.setClearColor(0x050505); 82 | 83 | setup.canvasDimensions = canvasDimensions; 84 | setup.renderer.setPixelRatio(canvasDimensions.pixelRatio); 85 | setup.renderer.setSize(canvasDimensions.width, canvasDimensions.height, false); 86 | setup.scene = new Scene(); 87 | 88 | setup.cameraDefaults = cameraDefaults; 89 | setup.cameraTarget = setup.cameraDefaults.posCameraTarget; 90 | setup.camera = new PerspectiveCamera(setup.cameraDefaults.fov, recalcAspectRatio(setup.canvasDimensions), 91 | setup.cameraDefaults.near, setup.cameraDefaults.far); 92 | resetCamera(setup); 93 | 94 | if (!isWorker() || forceControls) { 95 | setup.controls = new TrackballControls(setup.camera, setup.renderer.domElement); 96 | } 97 | 98 | const ambientLight = new AmbientLight(0x404040); 99 | const directionalLight1 = new DirectionalLight(0xC0C090); 100 | const directionalLight2 = new DirectionalLight(0xC0C090); 101 | 102 | directionalLight1.position.set(- 100, - 50, 100); 103 | directionalLight2.position.set(100, 50, - 100); 104 | 105 | setup.scene.add(directionalLight1); 106 | setup.scene.add(directionalLight2); 107 | setup.scene.add(ambientLight); 108 | 109 | const helper = new GridHelper(1200, 60, 0xFF4444, 0x404040); 110 | helper.name = 'grid'; 111 | setup.scene.add(helper); 112 | 113 | return setup; 114 | }; 115 | 116 | export const setCanvasDimensions = (offscreenPayload: OffscreenPayload) => { 117 | const data = offscreenPayload.message; 118 | const canvasDimensions = { 119 | width: data.width ?? 0, 120 | height: data.height ?? 0, 121 | pixelRatio: data.pixelRatio ?? 0 122 | }; 123 | return canvasDimensions; 124 | }; 125 | 126 | export const recalcAspectRatio = (canvasDimensions: CanvasDimensions) => { 127 | return (canvasDimensions.height === 0) ? 1 : canvasDimensions.width / canvasDimensions.height; 128 | }; 129 | 130 | export const resetCamera = (setup: ThreeDefaultSetup) => { 131 | setup.camera.position.copy(setup.cameraDefaults.posCamera); 132 | setup.cameraTarget.copy(setup.cameraDefaults.posCameraTarget); 133 | updateCamera(setup); 134 | }; 135 | 136 | export const updateCamera = (setup: ThreeDefaultSetup) => { 137 | setup.camera.aspect = recalcAspectRatio(setup.canvasDimensions); 138 | setup.camera.lookAt(setup.cameraTarget); 139 | setup.camera.updateProjectionMatrix(); 140 | }; 141 | 142 | export const resizeDisplayGL = (setup: ThreeDefaultSetup) => { 143 | setup.controls?.update(); 144 | setup.renderer.setSize(setup.canvasDimensions.width, setup.canvasDimensions.height, false); 145 | updateCamera(setup); 146 | }; 147 | 148 | export const renderDefault = (setup: ThreeDefaultSetup) => { 149 | if (!setup.renderer.autoClear) { 150 | setup.renderer.clear(); 151 | } 152 | setup.controls?.update(); 153 | setup.renderer.render(setup.scene, setup.camera); 154 | }; 155 | -------------------------------------------------------------------------------- /packages/examples/src/examples/OBJLoader2BasicExample.ts: -------------------------------------------------------------------------------- 1 | import { Object3D, Vector3 } from 'three'; 2 | import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; 3 | import { OBJLoader2, MtlObjBridge } from 'wwobjloader2'; 4 | import { CanvasDimensions, createThreeDefaultSetup, ExampleDefinition, renderDefault, reportProgress, ThreeDefaultSetup } from './ExampleCommons.js'; 5 | 6 | export class OBJLoader2BasicExample implements ExampleDefinition { 7 | 8 | private setup: ThreeDefaultSetup; 9 | private modelUrl?: string; 10 | private materialUrl?: string; 11 | 12 | constructor(canvas: HTMLCanvasElement | null, canvasDimensions: CanvasDimensions, forceControls: boolean) { 13 | const cameraDefaults = { 14 | posCamera: new Vector3(0.0, 175.0, 500.0), 15 | posCameraTarget: new Vector3(0, 0, 0), 16 | near: 0.1, 17 | far: 10000, 18 | fov: 45 19 | }; 20 | this.setup = createThreeDefaultSetup(canvas, cameraDefaults, canvasDimensions, forceControls); 21 | } 22 | 23 | setUrls(modelUrl: string, materialUrl?: string) { 24 | this.modelUrl = modelUrl; 25 | this.materialUrl = materialUrl; 26 | } 27 | 28 | getSetup() { 29 | return this.setup; 30 | } 31 | 32 | render() { 33 | renderDefault(this.setup); 34 | } 35 | 36 | run() { 37 | const modelName = 'female02'; 38 | reportProgress({ detail: { text: 'Loading: ' + modelName } }); 39 | 40 | const objLoader2 = new OBJLoader2(); 41 | const callbackOnLoad = (object3d: Object3D) => { 42 | this.setup.scene.add(object3d); 43 | reportProgress({ 44 | detail: { 45 | text: `Loading of [${modelName}] was successfully completed.` 46 | } 47 | }); 48 | }; 49 | 50 | if (this.materialUrl) { 51 | const onLoadMtl = (mtlParseResult: MTLLoader.MaterialCreator) => { 52 | objLoader2.setModelName(modelName); 53 | objLoader2.setLogging(true, true); 54 | objLoader2.setMaterials(MtlObjBridge.addMaterialsFromMtlLoader(mtlParseResult)); 55 | objLoader2.load(this.modelUrl!, callbackOnLoad); 56 | }; 57 | const mtlLoader = new MTLLoader(); 58 | mtlLoader.load(this.materialUrl, onLoadMtl); 59 | } else { 60 | objLoader2.setModelName(modelName); 61 | objLoader2.setLogging(true, false); 62 | objLoader2.load(this.modelUrl, callbackOnLoad); 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /packages/examples/src/examples/OBJLoader2BasicExampleOffscreen.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RawPayload, 3 | WorkerTask, 4 | WorkerTaskMessage, 5 | buildDefaultEventHandlingInstructions, 6 | initOffscreenCanvas, 7 | registerCanvas, 8 | registerResizeHandler 9 | } from 'wtd-core'; 10 | 11 | export class OBJLoader2BasicExampleOffscreen { 12 | 13 | async executeExample() { 14 | const taskName = 'OffscreenCanvasWorker'; 15 | 16 | // register the module worker 17 | const dev = import.meta.env?.DEV === true; 18 | const url = new URL(dev ? '../worker/BasicExampleOffscreenWorker.ts' : './worker/BasicExampleOffscreenWorker-es.js', import.meta.url); 19 | const worker = new Worker(url.href, { 20 | type: 'module' 21 | }); 22 | const workerTask = new WorkerTask({ 23 | taskName, 24 | workerId: 1, 25 | workerConfig: { 26 | $type: 'WorkerConfigDirect', 27 | worker 28 | }, 29 | verbose: true 30 | }); 31 | 32 | try { 33 | const canvas = document.getElementById('example') as HTMLCanvasElement; 34 | 35 | // only create worker, but do not init 36 | workerTask.connectWorker(); 37 | 38 | // delegate events from main to offscreen 39 | const handlingInstructions = buildDefaultEventHandlingInstructions(); 40 | await registerCanvas(workerTask, canvas, handlingInstructions); 41 | registerResizeHandler(workerTask, canvas); 42 | 43 | // provide the canvas to the worker 44 | await initOffscreenCanvas(workerTask, canvas); 45 | 46 | // once the init Promise returns enqueue the execution 47 | const rawPayload = new RawPayload(); 48 | rawPayload.message.raw = { 49 | modelUrl: new URL('./models/obj/main/female02/female02_vertex_colors.obj', window.location.href).href 50 | }; 51 | await workerTask.executeWorker({ 52 | message: WorkerTaskMessage.fromPayload(rawPayload), 53 | }); 54 | console.log('enqueueWorkerExecutionPlan finished successfully.'); 55 | } catch (e: unknown) { 56 | console.error(e); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /packages/examples/src/examples/OBJLoader2BugVerify.ts: -------------------------------------------------------------------------------- 1 | import { Object3D, Vector3 } from 'three'; 2 | import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; 3 | 4 | import { OBJLoader2, MtlObjBridge } from 'wwobjloader2'; 5 | import { ResourceDescriptor } from 'wwobjloader2'; 6 | import { AssetPipelineLoader, AssetPipeline, AssetTask, LinkType } from 'wwobjloader2'; 7 | import { createThreeDefaultSetup, ExampleDefinition, renderDefault, ThreeDefaultSetup } from './ExampleCommons.js'; 8 | 9 | export class OBJLoader2BugVerify implements ExampleDefinition { 10 | 11 | private setup: ThreeDefaultSetup; 12 | 13 | constructor(canvas: HTMLCanvasElement | null) { 14 | const cameraDefaults = { 15 | posCamera: new Vector3(0.0, 175.0, 500.0), 16 | posCameraTarget: new Vector3(0, 0, 0), 17 | near: 0.1, 18 | far: 10000, 19 | fov: 45 20 | }; 21 | this.setup = createThreeDefaultSetup(canvas, cameraDefaults, { 22 | width: canvas?.offsetWidth ?? 0, 23 | height: canvas?.offsetHeight ?? 0, 24 | pixelRatio: window.devicePixelRatio 25 | }); 26 | this.setup.renderer?.setClearColor(0x808080); 27 | } 28 | 29 | getSetup() { 30 | return this.setup; 31 | } 32 | 33 | render() { 34 | renderDefault(this.setup); 35 | } 36 | 37 | run() { 38 | // issue: 14 39 | const assetPipeline14 = new AssetPipeline(); 40 | const assetPipelineLoader14 = new AssetPipelineLoader('Issue14', assetPipeline14); 41 | 42 | const pivot14 = new Object3D(); 43 | pivot14.position.set(-400, 0, 0); 44 | pivot14.scale.set(5.0, 5.0, 5.0); 45 | this.setup.scene.add(pivot14); 46 | assetPipelineLoader14.setBaseObject3d(pivot14); 47 | 48 | const assetTask14Mtl = new AssetTask('task14Mtl'); 49 | assetTask14Mtl.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/14/bbd3874250e2414aaa6a4c84c8a21656.mtl').setNeedStringOutput(true)); 50 | assetTask14Mtl.setLoader(new MTLLoader(), { resourcePath: './models/obj/bugs/14/' }); 51 | 52 | const assetTask14MtlObjLink = new AssetTask('task14MtlObjLink'); 53 | assetTask14MtlObjLink.setLinker(new MtlObjBridge()); 54 | 55 | const assetTask14Obj = new AssetTask('task14Obj'); 56 | assetTask14Obj.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/14/bbd3874250e2414aaa6a4c84c8a21656.obj')); 57 | assetTask14Obj.setLoader(new OBJLoader2()); 58 | 59 | assetPipeline14.addAssetTask(assetTask14Mtl); 60 | assetPipeline14.addAssetTask(assetTask14MtlObjLink); 61 | assetPipeline14.addAssetTask(assetTask14Obj); 62 | 63 | assetPipelineLoader14.run(); 64 | 65 | // issue: 14032 66 | const assetPipeline14032 = new AssetPipeline(); 67 | const assetPipelineLoader14032 = new AssetPipelineLoader('Issue14032', assetPipeline14032); 68 | 69 | const pivot14032 = new Object3D(); 70 | pivot14032.position.set(-325, 50, 0); 71 | this.setup.scene.add(pivot14032); 72 | assetPipelineLoader14032.setBaseObject3d(pivot14032); 73 | 74 | const assetTask14032Mtl = new AssetTask('task14032Mtl'); 75 | assetTask14032Mtl.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/14032/union.mtl').setNeedStringOutput(true)); 76 | assetTask14032Mtl.setLoader(new MTLLoader(), { resourcePath: './models/obj/bugs/14032/' }); 77 | 78 | const assetTask14032MtlObjLink = new AssetTask('task14MtlObjLink'); 79 | assetTask14032MtlObjLink.setLinker(new MtlObjBridge()); 80 | 81 | const assetTask14032Obj = new AssetTask('task14032Obj'); 82 | assetTask14032Obj.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/14032/union.obj')); 83 | assetTask14032Obj.setLoader(new OBJLoader2()); 84 | 85 | assetPipeline14032.addAssetTask(assetTask14032Mtl); 86 | assetPipeline14032.addAssetTask(assetTask14032MtlObjLink); 87 | assetPipeline14032.addAssetTask(assetTask14032Obj); 88 | 89 | assetPipelineLoader14032.run(); 90 | 91 | // issue: 21 92 | const assetPipeline21 = new AssetPipeline(); 93 | const assetPipelineLoader21 = new AssetPipelineLoader('Issue21', assetPipeline21); 94 | const pivot21 = new Object3D(); 95 | pivot21.position.set(-225, 0, 0); 96 | pivot21.scale.set(25.0, 25.0, 25.0); 97 | this.setup.scene.add(pivot21); 98 | assetPipelineLoader21.setBaseObject3d(pivot21); 99 | 100 | const assetTask21Mtl = new AssetTask('task21Mtl'); 101 | assetTask21Mtl.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/21/Table_Photo_Frame_03.mtl').setNeedStringOutput(true)); 102 | assetTask21Mtl.setLoader(new MTLLoader(), { resourcePath: './models/obj/bugs/21/' }); 103 | 104 | const assetTask21MtlObjLink = new AssetTask('task21MtlObjLink'); 105 | assetTask21MtlObjLink.setLinker(new MtlObjBridge()); 106 | 107 | const assetTask21Obj = new AssetTask('task21Obj'); 108 | assetTask21Obj.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/21/Table_Photo_Frame_03.obj')); 109 | assetTask21Obj.setLoader(new OBJLoader2()); 110 | 111 | assetPipeline21.addAssetTask(assetTask21Mtl); 112 | assetPipeline21.addAssetTask(assetTask21MtlObjLink); 113 | assetPipeline21.addAssetTask(assetTask21Obj); 114 | 115 | assetPipelineLoader21.run(); 116 | 117 | // issue: 12120 118 | const assetPipeline12120 = new AssetPipeline(); 119 | const assetPipelineLoader12120 = new AssetPipelineLoader('Issue12120', assetPipeline12120); 120 | const pivot12120 = new Object3D(); 121 | pivot12120.position.set(-325, 0, -100); 122 | pivot12120.scale.set(0.01, 0.01, 0.01); 123 | this.setup.scene.add(pivot12120); 124 | assetPipelineLoader12120.setBaseObject3d(pivot12120); 125 | 126 | const assetTask12120Mtl = new AssetTask('task12120Mtl'); 127 | assetTask12120Mtl.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/12120/zjej_abm_f01_out_T005.mtl').setNeedStringOutput(true)); 128 | assetTask12120Mtl.setLoader(new MTLLoader(), { resourcePath: './models/obj/bugs/12120/' }); 129 | 130 | const assetTask12120MtlObjLink = new AssetTask('task12120MtlObjLink'); 131 | assetTask12120MtlObjLink.setLinker(new MtlObjBridge()); 132 | 133 | const assetTask12120Obj = new AssetTask('task12121Obj'); 134 | assetTask12120Obj.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/12120/zjej_abm_f01_out_T005.obj')); 135 | assetTask12120Obj.setLoader(new OBJLoader2()); 136 | 137 | assetPipeline12120.addAssetTask(assetTask12120Mtl); 138 | assetPipeline12120.addAssetTask(assetTask12120MtlObjLink); 139 | assetPipeline12120.addAssetTask(assetTask12120Obj); 140 | 141 | assetPipelineLoader12120.run(); 142 | 143 | // issue: 12324 144 | const assetPipeline12324 = new AssetPipeline(); 145 | const assetPipelineLoader12324 = new AssetPipelineLoader('Issue12324', assetPipeline12324); 146 | const pivot12324 = new Object3D(); 147 | pivot12324.position.set(-50, 0, 0); 148 | this.setup.scene.add(pivot12324); 149 | assetPipelineLoader12324.setBaseObject3d(pivot12324); 150 | 151 | const assetTask12324Mtl = new AssetTask('task12324Mtl'); 152 | assetTask12324Mtl.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/12324/rampanueva.mtl').setNeedStringOutput(true)); 153 | assetTask12324Mtl.setLoader(new MTLLoader(), { resourcePath: './models/obj/bugs/12324/' }); 154 | 155 | const assetTask12324MtlObjLink = new AssetTask('task12324MtlObjLink'); 156 | assetTask12324MtlObjLink.setLinker(new MtlObjBridge()); 157 | 158 | const assetTask12324Obj = new AssetTask('task12324Obj'); 159 | assetTask12324Obj.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/12324/rampanueva.obj')); 160 | assetTask12324Obj.setLoader(new OBJLoader2()); 161 | 162 | assetPipeline12324.addAssetTask(assetTask12324Mtl); 163 | assetPipeline12324.addAssetTask(assetTask12324MtlObjLink); 164 | assetPipeline12324.addAssetTask(assetTask12324Obj); 165 | 166 | assetPipelineLoader12324.run(); 167 | 168 | // issue: 11811A 169 | const assetPipeline11811A = new AssetPipeline(); 170 | const assetPipelineLoader11811A = new AssetPipelineLoader('Issue11811A', assetPipeline11811A); 171 | const pivot11811A = new Object3D(); 172 | pivot11811A.position.set(50, 0, 0); 173 | pivot11811A.scale.set(0.25, 0.25, 0.25); 174 | this.setup.scene.add(pivot11811A); 175 | assetPipelineLoader11811A.setBaseObject3d(pivot11811A); 176 | 177 | const assetTask11811AMtl = new AssetTask('task11811AMtl'); 178 | assetTask11811AMtl.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/11811/3dbpo10518T.mtl').setNeedStringOutput(true)); 179 | assetTask11811AMtl.setLoader(new MTLLoader(), { resourcePath: './models/obj/bugs/11811/' }); 180 | 181 | const assetTask11811AMtlObjLink = new AssetTask('task11811AMtlObjLink'); 182 | assetTask11811AMtlObjLink.setLinker(new MtlObjBridge() as unknown as LinkType); 183 | 184 | const assetTask11811AObj = new AssetTask('task11811AObj'); 185 | assetTask11811AObj.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/11811/3dbpo10518T.obj')); 186 | assetTask11811AObj.setLoader(new OBJLoader2()); 187 | 188 | assetPipeline11811A.addAssetTask(assetTask11811AMtl); 189 | assetPipeline11811A.addAssetTask(assetTask11811AMtlObjLink); 190 | assetPipeline11811A.addAssetTask(assetTask11811AObj); 191 | 192 | assetPipelineLoader11811A.run(); 193 | 194 | // issue: 11811B 195 | const assetPipeline11811B = new AssetPipeline(); 196 | const assetPipelineLoader11811B = new AssetPipelineLoader('Issue11811B', assetPipeline11811B); 197 | const pivot11811B = new Object3D(); 198 | pivot11811B.position.set(150, 0, 0); 199 | pivot11811B.scale.set(0.25, 0.25, 0.25); 200 | this.setup.scene.add(pivot11811B); 201 | assetPipelineLoader11811B.setBaseObject3d(pivot11811B); 202 | 203 | const assetTask11811BMtl = new AssetTask('task11811BMtl'); 204 | assetTask11811BMtl.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/11811/3dbts103601T.mtl').setNeedStringOutput(true)); 205 | assetTask11811BMtl.setLoader(new MTLLoader(), { resourcePath: './models/obj/bugs/11811/' }); 206 | 207 | const assetTask11811BMtlObjLink = new AssetTask('task11811BMtlObjLink'); 208 | assetTask11811BMtlObjLink.setLinker(new MtlObjBridge()); 209 | 210 | const assetTask11811BObj = new AssetTask('task11811BObj'); 211 | assetTask11811BObj.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/11811/3dbts103601T.obj')); 212 | assetTask11811BObj.setLoader(new OBJLoader2()); 213 | 214 | assetPipeline11811B.addAssetTask(assetTask11811BMtl); 215 | assetPipeline11811B.addAssetTask(assetTask11811BMtlObjLink); 216 | assetPipeline11811B.addAssetTask(assetTask11811BObj); 217 | 218 | assetPipelineLoader11811B.run(); 219 | 220 | // Issue: 27 door 221 | const assetPipeline27Door = new AssetPipeline(); 222 | const assetPipelineLoader27Door = new AssetPipelineLoader('Issue27Door', assetPipeline27Door); 223 | const pivot27Door = new Object3D(); 224 | pivot27Door.position.set(250, 0, 0); 225 | pivot27Door.scale.set(0.5, 0.5, 0.5); 226 | this.setup.scene.add(pivot27Door); 227 | assetPipelineLoader27Door.setBaseObject3d(pivot27Door); 228 | 229 | const assetTask27DoorMtl = new AssetTask('task27DoorMtl'); 230 | assetTask27DoorMtl.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/27/door.mtl').setNeedStringOutput(true)); 231 | assetTask27DoorMtl.setLoader(new MTLLoader(), { resourcePath: './models/obj/bugs/27/' }); 232 | 233 | const assetTask27DoorMtlObjLink = new AssetTask('task27DoorMtlObjLink'); 234 | assetTask27DoorMtlObjLink.setLinker(new MtlObjBridge()); 235 | 236 | const assetTask27DoorObj = new AssetTask('task27DoorObj'); 237 | assetTask27DoorObj.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/27/door.obj')); 238 | assetTask27DoorObj.setLoader(new OBJLoader2()); 239 | 240 | assetPipeline27Door.addAssetTask(assetTask27DoorMtl); 241 | assetPipeline27Door.addAssetTask(assetTask27DoorMtlObjLink); 242 | assetPipeline27Door.addAssetTask(assetTask27DoorObj); 243 | 244 | assetPipelineLoader27Door.run(); 245 | 246 | // Issue: 27 wall2 247 | const assetPipeline27Wall2 = new AssetPipeline(); 248 | const assetPipelineLoader27Wall2 = new AssetPipelineLoader('Issue27Wall2', assetPipeline27Wall2); 249 | const pivot27Wall2 = new Object3D(); 250 | pivot27Wall2.position.set(350, 0, 0); 251 | pivot27Wall2.scale.set(0.5, 0.5, 0.5); 252 | this.setup.scene.add(pivot27Wall2); 253 | assetPipelineLoader27Wall2.setBaseObject3d(pivot27Wall2); 254 | 255 | const assetTask27Wall2Mtl = new AssetTask('task27Wall2Mtl'); 256 | assetTask27Wall2Mtl.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/27/wall2.mtl').setNeedStringOutput(true)); 257 | assetTask27Wall2Mtl.setLoader(new MTLLoader(), { resourcePath: './models/obj/bugs/27/' }); 258 | 259 | const assetTask27Wall2MtlObjLink = new AssetTask('task27Wall2MtlObjLink'); 260 | assetTask27Wall2MtlObjLink.setLinker(new MtlObjBridge()); 261 | 262 | const assetTask27Wall2Obj = new AssetTask('task27Wall2Obj'); 263 | assetTask27Wall2Obj.setResourceDescriptor(new ResourceDescriptor('./models/obj/bugs/27/wall2.obj')); 264 | assetTask27Wall2Obj.setLoader(new OBJLoader2()); 265 | 266 | assetPipeline27Wall2.addAssetTask(assetTask27Wall2Mtl); 267 | assetPipeline27Wall2.addAssetTask(assetTask27Wall2MtlObjLink); 268 | assetPipeline27Wall2.addAssetTask(assetTask27Wall2Obj); 269 | 270 | assetPipelineLoader27Wall2.run(); 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /packages/examples/src/examples/OBJLoader2OBJLoaderCompareExample.ts: -------------------------------------------------------------------------------- 1 | import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; 2 | import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; 3 | 4 | import { OBJLoader2, MtlObjBridge } from 'wwobjloader2'; 5 | import { createThreeDefaultSetup, ExampleDefinition, renderDefault, reportProgress, ThreeDefaultSetup } from './ExampleCommons.js'; 6 | import { Object3D, Vector3 } from 'three'; 7 | 8 | export class OBJLoader2OBJLoaderCompareExample implements ExampleDefinition { 9 | 10 | private setup: ThreeDefaultSetup; 11 | 12 | constructor(canvas: HTMLCanvasElement | null) { 13 | const cameraDefaults = { 14 | posCamera: new Vector3(0.0, 175.0, 500.0), 15 | posCameraTarget: new Vector3(0, 0, 0), 16 | near: 0.1, 17 | far: 10000, 18 | fov: 45 19 | }; 20 | this.setup = createThreeDefaultSetup(canvas, cameraDefaults, { 21 | width: canvas?.offsetWidth ?? 0, 22 | height: canvas?.offsetHeight ?? 0, 23 | pixelRatio: window.devicePixelRatio 24 | }); 25 | } 26 | 27 | getSetup() { 28 | return this.setup; 29 | } 30 | 31 | render() { 32 | renderDefault(this.setup); 33 | } 34 | 35 | run() { 36 | const modelName = 'verificationCubes'; 37 | reportProgress({ detail: { text: 'Loading: ' + modelName } }); 38 | 39 | const objLoader = new OBJLoader(); 40 | 41 | const objLoader2 = new OBJLoader2(); 42 | objLoader2.setModelName(modelName); 43 | objLoader2.setLogging(true, false); 44 | objLoader2.setUseOAsMesh(true); 45 | 46 | const callbackOnLoad = (object3d: Object3D) => { 47 | this.setup.scene.add(object3d); 48 | console.log('Loading complete: ' + modelName); 49 | reportProgress({ detail: { text: 'Loading of [' + modelName + '] was successfully completed.' } }); 50 | 51 | }; 52 | 53 | const onLoadMtl = (mtlParseResult: MTLLoader.MaterialCreator) => { 54 | objLoader.setMaterials(mtlParseResult); 55 | objLoader.load('./models/obj/main/verify/verify.obj', (object) => { 56 | object.position.y = -100; 57 | this.setup.scene.add(object); 58 | }); 59 | 60 | objLoader2.setMaterials(MtlObjBridge.addMaterialsFromMtlLoader(mtlParseResult)); 61 | objLoader2.load('./models/obj/main/verify/verify.obj', callbackOnLoad); 62 | }; 63 | 64 | const mtlLoader = new MTLLoader(); 65 | mtlLoader.load('./models/obj/main/verify/verify.mtl', onLoadMtl); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /packages/examples/src/examples/OBJLoader2OptionsExample.ts: -------------------------------------------------------------------------------- 1 | import { BoxGeometry, DoubleSide, FileLoader, FrontSide, LineSegments, Material, Mesh, MeshNormalMaterial, Object3D, Points, Vector3 } from 'three'; 2 | import { VertexNormalsHelper } from 'three/examples/jsm/helpers/VertexNormalsHelper.js'; 3 | import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; 4 | import { Controller, GUI } from 'lil-gui'; 5 | import { OBJLoader2, OBJLoader2Parallel, MtlObjBridge } from 'wwobjloader2'; 6 | import { createThreeDefaultSetup, ExampleDefinition, renderDefault, reportProgress, ThreeDefaultSetup } from './ExampleCommons.js'; 7 | 8 | export class OBJLoader2OptionsExample implements ExampleDefinition { 9 | 10 | private setup: ThreeDefaultSetup; 11 | private cube: Mesh; 12 | private pivot: Object3D; 13 | private models = { 14 | male02: true, 15 | female02: true, 16 | female02_vertex_colors: true, 17 | waltHead: true, 18 | ninjaHead: true, 19 | cerberus: true 20 | }; 21 | private flatShading = false; 22 | private doubleSide = false; 23 | private useJsmWorker = false; 24 | private useIndices = false; 25 | private materialPerSmoothingGroup = false; 26 | private useOAsMesh = false; 27 | private disregardNormals = false; 28 | private regularLogging = false; 29 | private debugLogging = false; 30 | private loadCount = 6; 31 | 32 | constructor(canvas: HTMLCanvasElement | null) { 33 | const cameraDefaults = { 34 | posCamera: new Vector3(0.0, 175.0, 500.0), 35 | posCameraTarget: new Vector3(0, 0, 0), 36 | near: 0.1, 37 | far: 10000, 38 | fov: 45 39 | }; 40 | this.setup = createThreeDefaultSetup(canvas, cameraDefaults, { 41 | width: canvas?.offsetWidth ?? 0, 42 | height: canvas?.offsetHeight ?? 0, 43 | pixelRatio: window.devicePixelRatio 44 | }); 45 | 46 | const geometry = new BoxGeometry(10, 10, 10); 47 | const material = new MeshNormalMaterial(); 48 | this.cube = new Mesh(geometry, material); 49 | this.cube.position.set(0, 0, 0); 50 | this.setup.scene.add(this.cube); 51 | 52 | this.pivot = new Object3D(); 53 | this.pivot.name = 'Pivot'; 54 | this.setup.scene.add(this.pivot); 55 | } 56 | 57 | getSetup() { 58 | return this.setup; 59 | } 60 | 61 | useParseMain() { 62 | const modelName = 'female02'; 63 | reportProgress({ detail: { text: 'Loading: ' + modelName } }); 64 | 65 | const objLoader2 = new OBJLoader2() 66 | .setModelName(modelName) 67 | .setUseIndices(this.useIndices) 68 | .setMaterialPerSmoothingGroup(this.materialPerSmoothingGroup) 69 | .setUseOAsMesh(this.useOAsMesh) 70 | .setDisregardNormals(this.disregardNormals) 71 | .setLogging(this.regularLogging, this.debugLogging); 72 | 73 | const onLoadMtl = (mtlParseResult: MTLLoader.MaterialCreator) => { 74 | objLoader2.setMaterials(MtlObjBridge.addMaterialsFromMtlLoader(mtlParseResult)); 75 | 76 | const fileLoader = new FileLoader(); 77 | fileLoader.setPath(''); 78 | fileLoader.setResponseType('arraybuffer'); 79 | fileLoader.load('./models/obj/main/female02/female02.obj', 80 | (content) => { 81 | const local = new Object3D(); 82 | local.name = 'Pivot_female02'; 83 | local.position.set(75, 0, 0); 84 | this.pivot.add(local); 85 | local.add(objLoader2.parse(content)); 86 | 87 | reportProgress({ 88 | detail: { 89 | text: `Loading of ${modelName} completed: OBJLoader2#pase: Parsing completed` 90 | } 91 | }); 92 | this.finalize(); 93 | } 94 | ); 95 | }; 96 | 97 | const mtlLoader = new MTLLoader(); 98 | mtlLoader.load('./models/obj/main/female02/female02.mtl', onLoadMtl); 99 | } 100 | 101 | getWorkerUrl() { 102 | if (this.useJsmWorker) { 103 | return OBJLoader2Parallel.getModuleWorkerDefaultUrl(); 104 | } 105 | else { 106 | return OBJLoader2Parallel.getStandardWorkerDefaultUrl(); 107 | } 108 | } 109 | 110 | useParseParallel() { 111 | const modelName = 'female02_vertex'; 112 | reportProgress({ detail: { text: 'Loading: ' + modelName } }); 113 | 114 | const local = new Object3D(); 115 | local.name = 'Pivot_female02_vertex'; 116 | local.position.set(-75, 0, 0); 117 | this.pivot.add(local); 118 | 119 | const callbackOnLoad = (object3d: Object3D) => { 120 | this.setup.scene.add(object3d); 121 | reportProgress({ 122 | detail: { 123 | text: `Loading of [${modelName}] was successfully completed.` 124 | } 125 | }); 126 | this.finalize(); 127 | }; 128 | 129 | const objLoader2Parallel = new OBJLoader2Parallel() 130 | .setModelName(modelName) 131 | .setWorkerUrl(this.useJsmWorker, this.getWorkerUrl()) 132 | .setUseIndices(this.useIndices) 133 | .setMaterialPerSmoothingGroup(this.materialPerSmoothingGroup) 134 | .setUseOAsMesh(this.useOAsMesh) 135 | .setDisregardNormals(this.disregardNormals) 136 | .setLogging(this.regularLogging, this.debugLogging) 137 | .setCallbackOnLoad(callbackOnLoad); 138 | 139 | const fileLoader = new FileLoader(); 140 | fileLoader.setPath(''); 141 | fileLoader.setResponseType('arraybuffer'); 142 | fileLoader.load('./models/obj/main/female02/female02_vertex_colors.obj', 143 | (content) => { 144 | objLoader2Parallel.parse(content as ArrayBuffer); 145 | } 146 | ); 147 | } 148 | 149 | useLoadMain() { 150 | const modelName = 'male02'; 151 | reportProgress({ detail: { text: 'Loading: ' + modelName } }); 152 | 153 | const objLoader2 = new OBJLoader2() 154 | .setModelName(modelName) 155 | .setUseIndices(this.useIndices) 156 | .setMaterialPerSmoothingGroup(this.materialPerSmoothingGroup) 157 | .setUseOAsMesh(this.useOAsMesh) 158 | .setDisregardNormals(this.disregardNormals) 159 | .setLogging(this.regularLogging, this.debugLogging); 160 | 161 | const callbackOnLoad = (object3d: Object3D) => { 162 | const local = new Object3D(); 163 | local.name = 'Pivot_male02'; 164 | local.position.set(-75, 0, 0); 165 | this.pivot.add(local); 166 | local.add(object3d); 167 | reportProgress({ 168 | detail: { 169 | text: `Loading of [${modelName}] was successfully completed.` 170 | } 171 | }); 172 | this.finalize(); 173 | }; 174 | 175 | const onLoadMtl = (mtlParseResult: MTLLoader.MaterialCreator) => { 176 | objLoader2.setMaterials(MtlObjBridge.addMaterialsFromMtlLoader(mtlParseResult)); 177 | objLoader2.load('./models/obj/main/male02/male02.obj', callbackOnLoad); 178 | }; 179 | 180 | const mtlLoader = new MTLLoader(); 181 | mtlLoader.load('./models/obj/main/male02/male02.mtl', onLoadMtl); 182 | } 183 | 184 | useLoadParallel() { 185 | const modelName = 'WaltHead'; 186 | reportProgress({ detail: { text: 'Loading: ' + modelName } }); 187 | 188 | const local = new Object3D(); 189 | local.name = 'Pivot_WaltHead'; 190 | local.position.set(-75, 0, 100); 191 | const scale = 0.5; 192 | local.scale.set(scale, scale, scale); 193 | this.pivot.add(local); 194 | 195 | const objLoader2Parallel = new OBJLoader2Parallel() 196 | .setModelName(modelName) 197 | .setWorkerUrl(this.useJsmWorker, this.getWorkerUrl()) 198 | .setUseIndices(this.useIndices) 199 | .setMaterialPerSmoothingGroup(this.materialPerSmoothingGroup) 200 | .setUseOAsMesh(this.useOAsMesh) 201 | .setDisregardNormals(this.disregardNormals) 202 | .setLogging(this.regularLogging, this.debugLogging); 203 | 204 | const callbackOnLoad = (object3d: Object3D) => { 205 | local.add(object3d); 206 | reportProgress({ 207 | detail: { 208 | text: `Loading of [${modelName}] was successfully completed.` 209 | } 210 | }); 211 | this.finalize(); 212 | }; 213 | 214 | const onLoadMtl = (mtlParseResult: MTLLoader.MaterialCreator) => { 215 | objLoader2Parallel.setMaterials(MtlObjBridge.addMaterialsFromMtlLoader(mtlParseResult)); 216 | objLoader2Parallel.load('./models/obj/main/walt/WaltHead.obj', callbackOnLoad); 217 | }; 218 | 219 | const mtlLoader = new MTLLoader(); 220 | mtlLoader.load('./models/obj/main/walt/WaltHead.mtl', onLoadMtl); 221 | } 222 | 223 | async useLoadMainFallback() { 224 | const local = new Object3D(); 225 | local.name = 'Pivot_Cerberus'; 226 | local.position.set(0, 0, 100); 227 | const scale = 50; 228 | local.scale.set(scale, scale, scale); 229 | this.pivot.add(local); 230 | 231 | const objLoader2 = new OBJLoader2() 232 | .setModelName(local.name) 233 | .setUseIndices(this.useIndices) 234 | .setMaterialPerSmoothingGroup(this.materialPerSmoothingGroup) 235 | .setUseOAsMesh(this.useOAsMesh) 236 | .setDisregardNormals(this.disregardNormals) 237 | .setLogging(this.regularLogging, this.debugLogging); 238 | 239 | try { 240 | const object3d = await objLoader2.loadAsync('./models/obj/main/cerberus/Cerberus.obj'); 241 | local.add(object3d as Object3D); 242 | reportProgress({ 243 | detail: { 244 | text: `Loading of [${local.name}] was successfully completed.` 245 | } 246 | }); 247 | this.finalize(); 248 | console.log('Awaited Cerberus.obj loading!'); 249 | } catch (e) { 250 | console.error(e); 251 | } 252 | } 253 | 254 | useLoadParallelMeshAlter() { 255 | const local = new Object3D(); 256 | local.position.set(75, -150, 100); 257 | local.name = 'Pivot_ninjaHead'; 258 | this.pivot.add(local); 259 | 260 | const objLoader2Parallel = new OBJLoader2Parallel() 261 | .setModelName(local.name) 262 | .setWorkerUrl(this.useJsmWorker, this.getWorkerUrl()) 263 | .setUseIndices(this.useIndices) 264 | .setMaterialPerSmoothingGroup(this.materialPerSmoothingGroup) 265 | .setUseOAsMesh(this.useOAsMesh) 266 | .setDisregardNormals(this.disregardNormals) 267 | .setLogging(this.regularLogging, this.debugLogging) 268 | // Configure WorkerExecutionSupport to not disregard worker after execution 269 | .setTerminateWorkerOnLoad(false); 270 | 271 | const callbackMeshAlter = (mesh: Mesh | LineSegments | Points, baseObject3d: Object3D) => { 272 | const helper = new VertexNormalsHelper(mesh, 2, 0x00ff00); 273 | helper.name = 'VertexNormalsHelper'; 274 | baseObject3d.add(helper); 275 | }; 276 | objLoader2Parallel.setCallbackOnMeshAlter(callbackMeshAlter); 277 | 278 | const callbackOnLoad = (object3d: Object3D) => { 279 | local.add(object3d); 280 | reportProgress({ 281 | detail: { 282 | text: `Loading of [${objLoader2Parallel.getModelName()}] was successfully completed.` 283 | } 284 | }); 285 | this.finalize(); 286 | }; 287 | 288 | objLoader2Parallel.load('./models/obj/main/ninja/ninjaHead_Low.obj', callbackOnLoad); 289 | } 290 | 291 | finalize() { 292 | this.loadCount--; 293 | if (this.loadCount === 0) { 294 | reportProgress({ detail: { text: '' } }); 295 | } 296 | } 297 | render() { 298 | this.cube.rotation.x += 0.05; 299 | this.cube.rotation.y += 0.05; 300 | renderDefault(this.setup); 301 | } 302 | 303 | alterShading() { 304 | this.flatShading = !this.flatShading; 305 | console.log(this.flatShading ? 'Enabling flat shading' : 'Enabling smooth shading'); 306 | 307 | this.traversalFunction = (material: Material) => { 308 | if (Object.prototype.hasOwnProperty.call(material, 'flatShading')) { 309 | (material as { 310 | flatShading: boolean; 311 | } & Material).flatShading = this.flatShading; 312 | } 313 | material.needsUpdate = true; 314 | }; 315 | 316 | const scopeTraverse = (mesh: Object3D) => { 317 | this.traverseScene(mesh as Mesh); 318 | }; 319 | this.pivot.traverse(scopeTraverse); 320 | } 321 | 322 | traversalFunction(_material: Material) { 323 | console.log('Default traversalFunction'); 324 | } 325 | 326 | alterDouble() { 327 | this.doubleSide = !this.doubleSide; 328 | console.log(this.doubleSide ? 'Enabling DoubleSide materials' : 'Enabling FrontSide materials'); 329 | 330 | this.traversalFunction = (material: Material) => { 331 | material.side = this.doubleSide ? DoubleSide : FrontSide; 332 | }; 333 | 334 | const scopeTraverse = (mesh: Object3D) => { 335 | this.traverseScene(mesh as Mesh); 336 | }; 337 | this.pivot.traverse(scopeTraverse); 338 | } 339 | 340 | traverseScene(mesh: Mesh) { 341 | if (Array.isArray(mesh.material)) { 342 | const materials = mesh.material; 343 | 344 | for (const name in materials) { 345 | if (Object.prototype.hasOwnProperty.call(materials, name)) { 346 | this.traversalFunction(materials[name]); 347 | } 348 | } 349 | } else if (mesh.material) { 350 | this.traversalFunction(mesh.material); 351 | } 352 | } 353 | 354 | private executeLoading() { 355 | // Load a file with OBJLoader2.parse on main 356 | if (this.models.female02) this.useParseMain(); 357 | 358 | // Load a file with OBJLoader2Parallel.parse in parallel in worker 359 | if (this.models.female02_vertex_colors) this.useParseParallel(); 360 | 361 | // Load a file with OBJLoader.load on main 362 | if (this.models.male02) this.useLoadMain(); 363 | 364 | // Load a file with OBJLoader2Parallel.load in parallel in worker 365 | if (this.models.waltHead) this.useLoadParallel(); 366 | 367 | // Load a file with OBJLoader2Parallel.load on main with fallback to OBJLoader2.parse 368 | if (this.models.cerberus) this.useLoadMainFallback(); 369 | 370 | // Load a file with OBJLoader2Parallel.load in parallel in worker and add normals during onMeshAlter 371 | if (this.models.ninjaHead) this.useLoadParallelMeshAlter(); 372 | } 373 | 374 | run() { 375 | // eslint-disable-next-line @typescript-eslint/no-this-alias 376 | const app = this; 377 | const wwObjLoader2Control = { 378 | flatShading: app.flatShading, 379 | doubleSide: app.doubleSide, 380 | useJsmWorker: app.useJsmWorker, 381 | useIndices: app.useIndices, 382 | materialPerSmoothingGroup: app.materialPerSmoothingGroup, 383 | useOAsMesh: app.useOAsMesh, 384 | disregardNormals: app.disregardNormals, 385 | regularLogging: app.regularLogging, 386 | debugLogging: app.debugLogging, 387 | models: { 388 | male02: app.models.male02, 389 | female02: app.models.female02, 390 | female02_vertex_colors: app.models.female02_vertex_colors, 391 | waltHead: app.models.waltHead, 392 | ninjaHead: app.models.ninjaHead, 393 | cerberus: app.models.cerberus 394 | }, 395 | blockEvent: (event: Event) => { 396 | event.stopPropagation(); 397 | }, 398 | disableElement(elementHandle: Controller) { 399 | elementHandle.domElement.addEventListener('click', this.blockEvent, true); 400 | if (elementHandle.domElement.parentElement) { 401 | elementHandle.domElement.parentElement.style.pointerEvents = 'none'; 402 | elementHandle.domElement.parentElement.style.opacity = '0.5'; 403 | } 404 | }, 405 | enableElement(elementHandle: Controller) { 406 | if (elementHandle.domElement.parentElement) { 407 | elementHandle.domElement.removeEventListener('click', this.blockEvent, true); 408 | elementHandle.domElement.parentElement.style.pointerEvents = 'auto'; 409 | elementHandle.domElement.parentElement.style.opacity = '1.0'; 410 | } 411 | }, 412 | executeLoading: () => { 413 | if (app.models.female02 || app.models.female02_vertex_colors || app.models.male02 || 414 | app.models.waltHead || app.models.cerberus || app.models.ninjaHead) { 415 | 416 | app.executeLoading(); 417 | wwObjLoader2Control.disableElement(handleExecuteLoading); 418 | } 419 | }, 420 | }; 421 | 422 | const menuDiv = document.getElementById('dat'); 423 | const gui = new GUI({ 424 | autoPlace: false, 425 | width: 320 426 | }); 427 | menuDiv?.appendChild(gui.domElement); 428 | 429 | const folderObjLoader2Models = gui.addFolder('Model Selection'); 430 | 431 | const controlModelFemale02 = folderObjLoader2Models.add(wwObjLoader2Control.models, 'female02'); 432 | controlModelFemale02.onChange((v: boolean) => { 433 | console.log('Setting models.female02 to: ' + v); 434 | app.models.female02 = v; 435 | }); 436 | const controlModelFemale02VertexColors = folderObjLoader2Models.add(wwObjLoader2Control.models, 'female02_vertex_colors').name('female02 (worker)'); 437 | controlModelFemale02VertexColors.onChange((v: boolean) => { 438 | console.log('Setting models.female02_vertex_colors to: ' + v); 439 | app.models.female02_vertex_colors = v; 440 | }); 441 | const controlModelMale02 = folderObjLoader2Models.add(wwObjLoader2Control.models, 'male02'); 442 | controlModelMale02.onChange((v: boolean) => { 443 | console.log('Setting models.male02 to: ' + v); 444 | app.models.male02 = v; 445 | }); 446 | const controlModelWaltHead = folderObjLoader2Models.add(wwObjLoader2Control.models, 'waltHead').name('waltHead (worker)'); 447 | controlModelWaltHead.onChange((v: boolean) => { 448 | console.log('Setting models.waltHead to: ' + v); 449 | app.models.waltHead = v; 450 | }); 451 | const controlModelCerberus = folderObjLoader2Models.add(wwObjLoader2Control.models, 'cerberus'); 452 | controlModelCerberus.onChange((v: boolean) => { 453 | console.log('Setting models.cerberus to: ' + v); 454 | app.models.cerberus = v; 455 | }); 456 | const controlModelNinjaHead = folderObjLoader2Models.add(wwObjLoader2Control.models, 'ninjaHead').name('ninjaHead (worker)'); 457 | controlModelNinjaHead.onChange((v: boolean) => { 458 | console.log('Setting models.ninjaHead to: ' + v); 459 | app.models.ninjaHead = v; 460 | }); 461 | 462 | const folderObjLoader2ParallelOptions = gui.addFolder('OBJLoader2Parallel Options'); 463 | const controlJsmWorker = folderObjLoader2ParallelOptions.add(wwObjLoader2Control, 'useJsmWorker').name('Use Module Workers'); 464 | controlJsmWorker.onChange((value: boolean) => { 465 | console.log('Setting useJsmWorker to: ' + value); 466 | app.useJsmWorker = value; 467 | }); 468 | 469 | const folderObjLoader2ParserOptions = gui.addFolder('OBJLoader2Parser Options'); 470 | const controlUseIndices = folderObjLoader2ParserOptions.add(wwObjLoader2Control, 'useIndices').name('Use Indices'); 471 | controlUseIndices.onChange((value: boolean) => { 472 | console.log('Setting useIndices to: ' + value); 473 | app.useIndices = value; 474 | }); 475 | const controlMaterialPerSmoothingGroup = folderObjLoader2ParserOptions.add(wwObjLoader2Control, 'materialPerSmoothingGroup').name('Use material per SG'); 476 | controlMaterialPerSmoothingGroup.onChange((value: boolean) => { 477 | console.log('Setting materialPerSmoothingGroup to: ' + value); 478 | app.materialPerSmoothingGroup = value; 479 | }); 480 | const controlUseOAsMesh = folderObjLoader2ParserOptions.add(wwObjLoader2Control, 'useOAsMesh').name('Use useOAsMesh'); 481 | controlUseOAsMesh.onChange((value: boolean) => { 482 | console.log('Setting useOAsMesh to: ' + value); 483 | app.useOAsMesh = value; 484 | }); 485 | const controlDisregardNormals = folderObjLoader2ParserOptions.add(wwObjLoader2Control, 'disregardNormals').name('Use disregardNormals'); 486 | controlDisregardNormals.onChange((value: boolean) => { 487 | console.log('Setting disregardNormals to: ' + value); 488 | app.disregardNormals = value; 489 | }); 490 | 491 | const folderLoggingOptions = gui.addFolder('Logging'); 492 | const controlRegularLogging = folderLoggingOptions.add(wwObjLoader2Control, 'regularLogging').name('Enable logging'); 493 | const controlDebugLogging = folderLoggingOptions.add(wwObjLoader2Control, 'debugLogging').name('Enable debug logging'); 494 | 495 | controlRegularLogging.onChange((value: boolean) => { 496 | console.log('Setting regularLogging to: ' + value); 497 | app.regularLogging = value; 498 | if (!app.regularLogging) { 499 | wwObjLoader2Control.disableElement(controlDebugLogging); 500 | } else { 501 | wwObjLoader2Control.enableElement(controlDebugLogging); 502 | } 503 | }); 504 | controlDebugLogging.onChange((value: boolean) => { 505 | console.log('Setting debugLogging to: ' + value); 506 | 507 | app.debugLogging = value; 508 | if (!app.regularLogging) { 509 | app.regularLogging = app.debugLogging; 510 | controlRegularLogging.setValue(app.debugLogging); 511 | } 512 | }); 513 | wwObjLoader2Control.disableElement(controlDebugLogging); 514 | 515 | const folderRenderingOptions = gui.addFolder('Rendering Options'); 516 | const controlFlat = folderRenderingOptions.add(wwObjLoader2Control, 'flatShading').name('Flat Shading'); 517 | controlFlat.onChange((value: boolean) => { 518 | console.log('Setting flatShading to: ' + value); 519 | app.alterShading(); 520 | }); 521 | 522 | const controlDouble = folderRenderingOptions.add(wwObjLoader2Control, 'doubleSide').name('Double Side Materials'); 523 | controlDouble.onChange((value: boolean) => { 524 | console.log('Setting doubleSide to: ' + value); 525 | app.alterDouble(); 526 | }); 527 | 528 | const folderExecution = gui.addFolder('Execution'); 529 | const handleExecuteLoading = folderExecution.add(wwObjLoader2Control, 'executeLoading').name('Run'); 530 | handleExecuteLoading.domElement.id = 'startButton'; 531 | 532 | folderObjLoader2Models.open(); 533 | folderObjLoader2ParallelOptions.open(); 534 | folderObjLoader2ParserOptions.open(); 535 | folderLoggingOptions.close(); 536 | folderRenderingOptions.close(); 537 | folderExecution.open(); 538 | } 539 | 540 | } 541 | -------------------------------------------------------------------------------- /packages/examples/src/examples/OBJLoader2ParallelBasicExample.ts: -------------------------------------------------------------------------------- 1 | import { Object3D, Vector3 } from 'three'; 2 | import { OBJLoader2Parallel } from 'wwobjloader2'; 3 | import { createThreeDefaultSetup, ExampleDefinition, renderDefault, reportProgress, ThreeDefaultSetup } from './ExampleCommons.js'; 4 | 5 | export class OBJLoader2ParallelBasicExample implements ExampleDefinition { 6 | 7 | private setup: ThreeDefaultSetup; 8 | 9 | constructor(canvas: HTMLCanvasElement | null) { 10 | const cameraDefaults = { 11 | posCamera: new Vector3(0.0, 175.0, 500.0), 12 | posCameraTarget: new Vector3(0, 0, 0), 13 | near: 0.1, 14 | far: 10000, 15 | fov: 45 16 | }; 17 | this.setup = createThreeDefaultSetup(canvas, cameraDefaults, { 18 | width: canvas?.offsetWidth ?? 0, 19 | height: canvas?.offsetHeight ?? 0, 20 | pixelRatio: window.devicePixelRatio 21 | }); 22 | } 23 | 24 | getSetup() { 25 | return this.setup; 26 | } 27 | 28 | render() { 29 | renderDefault(this.setup); 30 | } 31 | 32 | run() { 33 | const modelName = 'female02_vertex'; 34 | reportProgress({ detail: { text: 'Loading: ' + modelName } }); 35 | 36 | const objLoader2Parallel = new OBJLoader2Parallel() 37 | .setModelName(modelName) 38 | .setLogging(true, true) 39 | .setUseIndices(true); 40 | 41 | const callbackOnLoad = (object3d: Object3D) => { 42 | this.setup.scene.add(object3d); 43 | reportProgress({ 44 | detail: { 45 | text: `Loading of [${modelName}] was successfully completed.` 46 | } 47 | }); 48 | 49 | }; 50 | const filename = './models/obj/main/female02/female02_vertex_colors.obj'; 51 | objLoader2Parallel.load(filename, callbackOnLoad); 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /packages/examples/src/examples/ReactExample.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { useLoader, Canvas } from '@react-three/fiber'; 4 | import { Object3D, TextureLoader } from 'three'; 5 | import { OBJLoader2React } from './ReactHelper.js'; 6 | 7 | function Model(_props: unknown) { 8 | const obj = useLoader( 9 | OBJLoader2React, 10 | './models/obj/main/female02/female02.obj' 11 | ); 12 | const texture = useLoader( 13 | TextureLoader, 14 | './models/obj/main/female02/uv_grid_opengl.jpg' 15 | ); 16 | 17 | return ( 18 | 19 | ${(obj as Object3D).children.map( 20 | (mesh: Object3D, i: number) => 21 | )} 22 | ); 23 | } 24 | 25 | const root = ReactDOM.createRoot(document.getElementById('root')!); 26 | root.render( 27 | ( 28 | 29 | 30 | 31 | 32 | )); 33 | -------------------------------------------------------------------------------- /packages/examples/src/examples/ReactExampleMtl.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { useLoader, Canvas } from '@react-three/fiber'; 4 | import { Object3D } from 'three'; 5 | import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; 6 | import { MtlObjBridge } from 'wwobjloader2'; 7 | import { OBJLoader2React } from './ReactHelper.js'; 8 | 9 | function Model(_props: unknown) { 10 | const obj = useLoader( 11 | OBJLoader2React, 12 | './models/obj/main/female02/female02.obj', 13 | (loader) => { 14 | const mtlLoader = new MTLLoader(); 15 | mtlLoader.load( 16 | './models/obj/main/female02/female02.mtl', 17 | (mtl) => { 18 | loader.setModelName('female02'); 19 | loader.setMaterials( 20 | MtlObjBridge.addMaterialsFromMtlLoader(mtl) 21 | ); 22 | } 23 | ); 24 | } 25 | ); 26 | 27 | return ( 28 | 29 | ${(obj as Object3D).children.map( 30 | (mesh: Object3D, i: number) => 31 | )} 32 | ); 33 | } 34 | 35 | const root = ReactDOM.createRoot(document.getElementById('root')!); 36 | root.render( 37 | ( 38 | 39 | 40 | 41 | 42 | )); 43 | -------------------------------------------------------------------------------- /packages/examples/src/examples/ReactHelper.ts: -------------------------------------------------------------------------------- 1 | import { CallbackOnLoadType, CallbackOnMeshAlterType, FileLoaderOnErrorType, FileLoaderOnProgressType, OBJLoader2 } from 'wwobjloader2'; 2 | 3 | export class OBJLoader2React extends OBJLoader2 { 4 | load(url: string, onLoad?: CallbackOnLoadType, onProgress?: FileLoaderOnProgressType, onError?: FileLoaderOnErrorType, onMeshAlter?: CallbackOnMeshAlterType) { 5 | OBJLoader2.prototype.load.call(this, url, onLoad!, onProgress, onError, onMeshAlter); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/examples/src/worker/BasicExampleOffscreenWorker.ts: -------------------------------------------------------------------------------- 1 | import { 2 | comRouting, 3 | getOffscreenCanvas, 4 | OffscreenPayload, 5 | OffscreenWorker, 6 | OffscreenWorkerCommandResponse, 7 | RawPayload, 8 | WorkerTaskMessage, 9 | WorkerTaskWorker 10 | } from 'wtd-core'; 11 | import { OBJLoader2BasicExample } from '../examples/OBJLoader2BasicExample.js'; 12 | import { executeExample, resizeDisplayGL, setCanvasDimensions } from '../examples/ExampleCommons.js'; 13 | import { ElementProxyReceiver, proxyStart } from 'wtd-three-ext'; 14 | 15 | export class HelloWorlThreeWorker implements WorkerTaskWorker, OffscreenWorker { 16 | 17 | private objLoader2BasicExample?: OBJLoader2BasicExample; 18 | private offScreenCanvas?: OffscreenCanvas; 19 | private eventProxy?: ElementProxyReceiver; 20 | 21 | proxyStart(message: WorkerTaskMessage) { 22 | console.log(`Received start command: ${message.cmd}`); 23 | this.eventProxy = new ElementProxyReceiver(); 24 | proxyStart(this.eventProxy); 25 | 26 | const proxyStartComplete = WorkerTaskMessage.createFromExisting(message, { 27 | overrideCmd: OffscreenWorkerCommandResponse.PROXY_START_COMPLETE 28 | }); 29 | self.postMessage(proxyStartComplete); 30 | } 31 | 32 | proxyEvent(message: WorkerTaskMessage) { 33 | const payload = message.payloads?.[0]; 34 | const offscreenPayload = (payload as OffscreenPayload); 35 | const event = offscreenPayload.message.event; 36 | if (event) { 37 | this.eventProxy?.handleEvent(event); 38 | } 39 | } 40 | 41 | resize(message: WorkerTaskMessage) { 42 | const offscreenPayload = message.payloads?.[0] as OffscreenPayload; 43 | 44 | if (this.objLoader2BasicExample) { 45 | this.objLoader2BasicExample.getSetup().canvasDimensions = setCanvasDimensions(offscreenPayload); 46 | resizeDisplayGL(this.objLoader2BasicExample.getSetup()); 47 | } 48 | } 49 | 50 | initOffscreenCanvas(message: WorkerTaskMessage): void { 51 | const offscreenPayload = message.payloads?.[0] as OffscreenPayload; 52 | this.offScreenCanvas = getOffscreenCanvas(offscreenPayload); 53 | 54 | this.eventProxy!.merge(this.offScreenCanvas!); 55 | 56 | const canvasDimensions = setCanvasDimensions(offscreenPayload); 57 | this.objLoader2BasicExample = new OBJLoader2BasicExample(this.eventProxy! as unknown as HTMLCanvasElement, 58 | canvasDimensions, true); 59 | 60 | const initOffscreenCanvasComplete = WorkerTaskMessage.createFromExisting(message, { 61 | overrideCmd: OffscreenWorkerCommandResponse.INIT_OFFSCREEN_CANVAS_COMPLETE 62 | }); 63 | self.postMessage(initOffscreenCanvasComplete); 64 | } 65 | 66 | execute(message: WorkerTaskMessage) { 67 | console.log(`HelloWorldWorker#execute: name: ${message.name} id: ${message.uuid} cmd: ${message.cmd} workerId: ${message.workerId}`); 68 | 69 | const rawPayload = message.payloads[0] as RawPayload; 70 | this.objLoader2BasicExample!.setUrls(rawPayload.message.raw.modelUrl as string); 71 | 72 | executeExample(this.objLoader2BasicExample!); 73 | } 74 | 75 | } 76 | 77 | const worker = new HelloWorlThreeWorker(); 78 | self.onmessage = message => comRouting(worker, message); 79 | -------------------------------------------------------------------------------- /packages/examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist", 6 | "declarationDir": "dist" 7 | }, 8 | "references": [ 9 | { "path": "../objloader2/tsconfig.json" } 10 | ], 11 | "include": [ 12 | "src/**/*" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/examples/verify_assetpipeline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | three.js webgl - AutoAssetLoader example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | AutoAssetLoader example 14 |
15 |
16 | 17 | 28 | 29 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /packages/examples/verify_assetpipeline_obj_stage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | three.js webgl - OBJLoader2 Stage 5 | 6 | 7 | 8 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 | OBJLoader2 Stage 30 |
31 |
32 | 33 | 34 | 46 | 47 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /packages/examples/verify_obj2_basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2 basic usage 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | OBJLoader2 basic usage 14 |
15 |
16 | 17 | 27 | 28 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/examples/verify_obj2_basic_offscreen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2 basic usage 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | OBJLoader2 basic usage 14 |
15 |
16 | 17 | 28 | 29 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /packages/examples/verify_obj2_options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2 usage options 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | OBJLoader2 usage options 17 |
18 |
19 | 20 | 32 | 33 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /packages/examples/verify_obj2parallel_basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OBJLoader2Parallel Worker Module Support 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | OBJLoader2Parallel Worker Module Support 14 |
15 |
16 | 17 | 26 | 27 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/examples/vite.config.production.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import path from 'path'; 3 | 4 | export default defineConfig(({ command }) => { 5 | console.log(`Running: ${command}`); 6 | return { 7 | build: { 8 | target: ['es2022'], 9 | rollupOptions: { 10 | input: { 11 | main: path.resolve(__dirname, 'index.html'), 12 | obj2_basic: path.resolve(__dirname, 'obj2_basic.html'), 13 | obj2_basic_offscreen: path.resolve(__dirname, 'obj2_basic_offscreen.html'), 14 | obj2parallel_basic: path.resolve(__dirname, 'obj2parallel_basic.html'), 15 | obj2_options: path.resolve(__dirname, 'obj2_options.html'), 16 | obj2_obj_compare: path.resolve(__dirname, 'obj2_obj_compare.html'), 17 | assetpipeline: path.resolve(__dirname, 'assetpipeline.html'), 18 | obj2_react: path.resolve(__dirname, 'obj2_react.html'), 19 | obj2_react_mtl: path.resolve(__dirname, 'obj2_react_mtl.html') 20 | }, 21 | output: { 22 | esModule: true 23 | } 24 | }, 25 | minify: false, 26 | assetsInlineLimit: 128, 27 | outDir: path.resolve(__dirname, 'production'), 28 | emptyOutDir: true, 29 | }, 30 | base: 'https://kaisalmen.github.io/WWOBJLoader/', 31 | optimizeDeps: { 32 | esbuildOptions: { 33 | target: 'es2022' 34 | } 35 | } 36 | }; 37 | }); 38 | -------------------------------------------------------------------------------- /packages/objloader2/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2022 Kai Salmen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/objloader2/README.md: -------------------------------------------------------------------------------- 1 | # OBJLoader2 & OBJLoader2Parallel for three.js 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/kaisalmen/WWOBJLoader/blob/dev/LICENSE) 4 | [![WWOBJLoader](https://github.com/kaisalmen/WWOBJLoader/actions/workflows/actions.yml/badge.svg)](https://github.com/kaisalmen/WWOBJLoader/actions/workflows/actions.yml) 5 | [![Github Pages](https://img.shields.io/badge/GitHub-Pages-blue?logo=github)](https://kaisalmen.github.io/WWOBJLoader) 6 | [![NPM Version](https://img.shields.io/npm/v/wwobjloader2.svg)](https://www.npmjs.com/package/wwobjloader2) 7 | [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/kaisalmen/WWOBJLoader) 8 | 9 | **OBJLoader2** is a loader for the **OBJ** file format. It is an alternative to [OBJLoader](https://github.com/mrdoob/three.js/blob/dev/examples/jsm/loaders/OBJLoader.js) included in [three.js](https://threejs.org). The loader and its parser can be used on Main via **OBJLoader2** or in parallel inside a web worker via **OBJLoader2Parallel**. 10 | 11 | ## Project History 12 | 13 | New versions of **OBJLoader2** and **OBJLoader2Parallel** are from now on again released as npm modules independent of three.js. The first stable version that was released independent again is 4.0.0. Versions 3.x.y were never released as independent npm and only in combination with three.ts itself. 14 | 15 | Between release of version 5.0.0 and 6.0.0 all code has been transformed to TypeScript. 16 | 17 | ### Changelog 18 | 19 | Interested in recent changes? Check the [CHANGELOG](https://github.com/kaisalmen/WWOBJLoader/blob/main/CHANGELOG.md). 20 | 21 | ## Development 22 | 23 | ### Getting Started 24 | 25 | There exist three possibilities: 26 | 27 | * Checkout the repository and run `npm install`, `npm run build` and then `npm run dev` to spin up local Vite dev server 28 | * Press the `Gitpod` button above and start coding and using the examples directly in the browser 29 | * Checkout the repository and use `docker-compose up -d` to spin up local Vite dev server. 30 | 31 | Whatever environment you choose to start [Vite](https://vitejs.dev/) is used to serve the code and the examples using it. With this setup you are able to change the code and examples without invoking an additional bundler. Vite ensures all imported npm modules are available if previously installed in local environment (see `npm install`). 32 | 33 | If you run Vite locally you require a **nodejs LTS** and **npm**. The Gitpod and local docker environment ensure all prerequisites are fulfilled. 34 | 35 | In any environment the server is reachable on port 8085. 36 | 37 | ### Examples 38 | 39 | If you want to get started see take a look at the following examples. They get more advanced from top to bottom: 40 | 41 | * OBJLoader2 basic: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_basic.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2BasicExample.ts)] 42 | * OBJLoader2 basic (Offscreen Canvas): [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_basic_offscreen.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2BasicExampleOffscreen.ts)] 43 | * OBJLoader2Parallel basic: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2parallel_basic.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2ParallelBasicExample.ts)] 44 | * OBJLoader2 usage options: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_options.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2OptionsExample.ts)] 45 | * OBJLoader2 / OBJLoader parser capability comparison: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_obj_compare.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2OBJLoaderCompareExample.ts)] 46 | * OBJLoader2 in react with a .jpg texture: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_react.html)] [[tsx](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/ReactExample.tsx)] 47 | * OBJLoader2 in react with a .mtl material: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/obj2_react-mtl.html)] [[tsx](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/ReactExampleMtl.tsx)] 48 | * AssetPipelineLoader basic example: [[html](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/assetpipeline.html)] [[ts](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/AssetPipelineLoaderExample.ts)] 49 | 50 | Try out all examples here: 51 | 52 | ### Main Branch 53 | 54 | Main development now takes place on branch [main](https://github.com/kaisalmen/WWOBJLoader/tree/main). Tags identify the releases. The stable branch has been retired. 55 | 56 | ## Feature Overview 57 | 58 | ### OBJLoader2Parser 59 | 60 | The parser `OBJLoader2Parser` used by `OBJLoader2` and `OBJLoader2Parallel` has all OBJ parsing capabilities of `OBJLoader` from three.js, plus some extra feature. Please see the following list: 61 | 62 | * The `parse` methods of `OBJLoader2Parser` accepts `ArrayBuffer` or `String` as input. Text processing is approx. 15-20 pecent slower. 63 | * In case `OBJLoader2Parallel` the of Parser `OBJLoader2Parser` is executed inside a worker. 64 | * `OBJLoader2Parser` features indexed rendering including vertex reduction. 65 | * Indexed rendering is available if switched on via `setUseIndices` (see `useIndices` in example **[OBJLoader2 usage options](https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/examples/src/examples/OBJLoader2OptionsExample.ts#L63)**). 66 | * Face N-Gons are supported. 67 | * Multi-Materials are created when needed. 68 | * Flat smoothing defined by "s 0" or "s off" is supported and Multi-Material is created when one object/group defines both smoothing groups equal and not equal to zero. 69 | * Support for points and lines is available since V2.3.0. 70 | * New mesh detection relies on 'g' occurrence or 'f', 'l' or 'p' type change (since V2.3.0). This allows multiple mesh definitions within one group. 71 | * Negative face indices are supported (issue #28) 72 | * The parser is now a single class that can be directly stored as string and therefore embedded in module or standard Workers (since V4.0.0). 73 | 74 | ## WorkerTaskDirector Core Library 75 | 76 | `WorkerTask` from [wtd-core](https://github.com/kaisalmen/wtd/tree/main/packages/wtd-core) is used to control everything regarding workers. This library was separated with the 4.0.0 release. It now evolves as independent library that is utilized by `OBJLoader2Parallel`. 77 | 78 | ## Docs 79 | 80 | Run `npm run doc` to create the documentation in [directory]((./packages/objloader2/docs). 81 | 82 | Happy coding! 83 | 84 | Kai 85 | -------------------------------------------------------------------------------- /packages/objloader2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wwobjloader2", 3 | "version": "6.2.1", 4 | "type": "module", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "types": "./dist/index.d.ts", 11 | "default": "./dist/index.js" 12 | }, 13 | "./worker": { 14 | "types": "./dist/worker/OBJLoader2Worker.d.ts", 15 | "default": "./dist/worker/OBJLoader2Worker.js" 16 | }, 17 | "./bundle": { 18 | "types": "./dist/index.d.ts", 19 | "default": "./lib/objloader2.js" 20 | }, 21 | "./bundle/worker/module": { 22 | "types": "./dist/worker/OBJLoader2Worker.d.ts", 23 | "default": "./lib/worker/OBJLoader2WorkerModule.js" 24 | }, 25 | "./bundle/worker/classic": { 26 | "types": "./dist/worker/OBJLoader2Worker.d.ts", 27 | "default": "./lib/worker/OBJLoader2WorkerClassic.js" 28 | } 29 | }, 30 | "typesVersions": { 31 | "*": { 32 | ".": [ 33 | "dist/index" 34 | ], 35 | "worker": [ 36 | "dist/worker/OBJLoader2Worker" 37 | ], 38 | "bundle": [ 39 | "dist/index" 40 | ], 41 | "bundle/worker/module": [ 42 | "dist/worker/OBJLoader2Worker" 43 | ], 44 | "bundle/worker/classic": [ 45 | "dist/worker/OBJLoader2Worker" 46 | ] 47 | } 48 | }, 49 | "files": [ 50 | "dist", 51 | "lib", 52 | "src", 53 | "LICENSE", 54 | "README.md" 55 | ], 56 | "scripts": { 57 | "clean": "shx rm -rf *.tsbuildinfo ./dist ./lib ./src/worker/OBJLoader2WorkerClassic.js ./src/worker/OBJLoader2WorkerModule.js ./docs", 58 | "doc": "shx rm -fr docs && typedoc --plugin typedoc-plugin-markdown --out docs src/index.ts", 59 | "build:worker:copy": "shx mkdir -p ./lib/worker && shx cp ./src/worker/OBJLoader2WorkerModule.js ./lib/worker/ && shx cp ./src/worker/OBJLoader2WorkerClassic.js ./lib/worker/", 60 | "build:worker": "vite -c vite.config.worker.ts build", 61 | "build:lib": "vite build", 62 | "compile": "tsc", 63 | "watch": "tsc -w", 64 | "build": "npm run clean && npm run compile && npm run build:lib && npm run build:worker" 65 | }, 66 | "volta": { 67 | "node": "20.12.2", 68 | "npm": "10.5.0" 69 | }, 70 | "dependencies": { 71 | "three": "~0.163.0", 72 | "wtd-core": "~3.0.0", 73 | "wtd-three-ext": "~3.0.0" 74 | }, 75 | "peerDependencies": { 76 | "three": ">= 0.137.5 < 1" 77 | }, 78 | "devDependencies": { 79 | "@types/three": "~0.163.0" 80 | }, 81 | "repository": { 82 | "type": "git", 83 | "url": "https://github.com/kaisalmen/WWOBJLoader", 84 | "directory": "packages/objloader2" 85 | }, 86 | "homepage": "https://github.com/kaisalmen/WWOBJLoader/blob/main/packages/objloader2/README.md", 87 | "bugs": "https://github.com/kaisalmen/WWOBJLoader/issues", 88 | "author": { 89 | "name": "kaisalmen", 90 | "url": "https://www.kaisalmen.de" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/objloader2/src/AssetPipelineLoader.ts: -------------------------------------------------------------------------------- 1 | import { FileLoader, Loader, Object3D } from 'three'; 2 | import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; 3 | import { AssociatedArrayType, applyProperties } from 'wtd-core'; 4 | import { ResourceDescriptor } from './utils/ResourceDescriptor.js'; 5 | 6 | export type CallbackCompleteType = ((description: string, extra?: Object3D) => void) | null; 7 | 8 | export type ParserType = AssociatedArrayType & { 9 | parse: (data: ArrayBufferLike | string) => Object3D; 10 | } 11 | 12 | export type LinkType = { 13 | link: (data: AssociatedArrayType, nextTask: AssociatedArrayType) => Object3D | undefined; 14 | } 15 | 16 | class AssetPipelineLoader { 17 | 18 | private name: string; 19 | private assetPipeline: AssetPipeline; 20 | private baseObject3d: Object3D | undefined; 21 | private onComplete: CallbackCompleteType = null; 22 | 23 | constructor(name: string, assetPipeline: AssetPipeline) { 24 | this.name = name; 25 | this.assetPipeline = assetPipeline; 26 | } 27 | 28 | getName() { 29 | return this.name; 30 | } 31 | 32 | setBaseObject3d(baseObject3d: Object3D) { 33 | this.baseObject3d = baseObject3d; 34 | return this; 35 | } 36 | 37 | setOnComplete(onComplete: CallbackCompleteType) { 38 | this.onComplete = onComplete; 39 | } 40 | 41 | run() { 42 | this.assetPipeline.initPipeline(this.name, this.onComplete); 43 | if (this.baseObject3d) { 44 | this.assetPipeline.runPipeline(this.baseObject3d); 45 | } else { 46 | throw new Error('baseObject3d was not properly specified.'); 47 | } 48 | 49 | return this; 50 | } 51 | 52 | } 53 | 54 | /** 55 | * The AssetPipeline defines a set of {@link AssetTask} that need to be executed one after the other and return a {@link Object3D}. 56 | * @constructor 57 | */ 58 | class AssetPipeline { 59 | 60 | private name: string | null = null; 61 | private onComplete: CallbackCompleteType = null; 62 | private assetTasks = new Map(); 63 | 64 | addAssetTask(assetTask: AssetTask) { 65 | this.assetTasks.set(assetTask.getName(), assetTask); 66 | return this; 67 | } 68 | 69 | /** 70 | * Init all {@link AssetTask} 71 | * 72 | * @param {string} name Name of the pipeline 73 | * @param {CallbackCompleteType} onComplete set callback function 74 | * @return {AssetPipeline} 75 | */ 76 | initPipeline(name: string, onComplete: CallbackCompleteType) { 77 | this.name = name; 78 | this.onComplete = onComplete; 79 | let assetTaskBefore: AssetTask | null = null; 80 | for (const assetTask of this.assetTasks.values()) { 81 | if (assetTaskBefore !== null) { 82 | assetTask.setTaskBefore(assetTaskBefore); 83 | assetTaskBefore.setTaskAfter(assetTask); 84 | } 85 | assetTaskBefore = assetTask; 86 | assetTask.init(); 87 | } 88 | return this; 89 | } 90 | 91 | /** 92 | * Run the pipeline: First load resources and then execute the parsing functions 93 | * @param {Object3D} baseObject3d 94 | * @return {AssetPipeline} 95 | */ 96 | async runPipeline(baseObject3d: Object3D) { 97 | const onComplete = this.onComplete ? this.onComplete : (x: string) => { 98 | console.log('Done loading: ' + x); 99 | }; 100 | 101 | const loadResources = async (assetTasks: Map) => { 102 | const loadPromises: Array> = []; 103 | for (const assetTask of assetTasks.values()) { 104 | if (assetTask.getResourceDescriptor()) { 105 | const promise = assetTask.loadResource(); 106 | loadPromises.push(promise); 107 | } 108 | } 109 | console.log('Waiting for completion of loading of all assets!'); 110 | return await Promise.all(loadPromises); 111 | }; 112 | 113 | const processAssets = (loadResults: ResourceDescriptor[]) => { 114 | console.log('Count of loaded resources: ' + loadResults.length); 115 | let assetTask; 116 | for (assetTask of this.assetTasks.values()) { 117 | // TODO: process must be async, so we can process worker based workloads 118 | assetTask.process(); 119 | } 120 | // last assetTask 121 | if (assetTask && assetTask.getProcessResult()) { 122 | baseObject3d.add(assetTask.getProcessResult()!); 123 | } 124 | 125 | return baseObject3d; 126 | }; 127 | 128 | try { 129 | const resourceDescriptor = await loadResources(this.assetTasks); 130 | const object3d = processAssets(resourceDescriptor); 131 | onComplete(this.name!, object3d); 132 | } catch (e) { 133 | console.error(e); 134 | } 135 | } 136 | 137 | } 138 | 139 | class AssetTask { 140 | 141 | private name: string; 142 | private resourceDescriptor: ResourceDescriptor | undefined; 143 | private assetLoader = { 144 | loader: { 145 | instance: undefined as ParserType | undefined, 146 | config: {} as AssociatedArrayType 147 | }, 148 | linker: undefined as LinkType | undefined 149 | }; 150 | private relations = { 151 | before: undefined as AssetTask | undefined, 152 | after: undefined as AssetTask | undefined, 153 | }; 154 | private processResult: Object3D | undefined = undefined; 155 | 156 | constructor(name: string) { 157 | this.name = name; 158 | } 159 | 160 | getName() { 161 | return this.name; 162 | } 163 | 164 | setResourceDescriptor(resourceDescriptor: ResourceDescriptor) { 165 | this.resourceDescriptor = resourceDescriptor; 166 | return this; 167 | } 168 | 169 | getResourceDescriptor() { 170 | return this.resourceDescriptor; 171 | } 172 | 173 | setTaskBefore(assetTask: AssetTask) { 174 | this.relations.before = assetTask; 175 | } 176 | 177 | setTaskAfter(assetTask: AssetTask) { 178 | this.relations.after = assetTask; 179 | } 180 | 181 | getProcessResult() { 182 | return this.processResult; 183 | } 184 | 185 | setLinker(linker: LinkType) { 186 | this.assetLoader.linker = linker; 187 | } 188 | 189 | setLoader(loader: Loader | Loader, loaderConfig?: AssociatedArrayType) { 190 | const parser = loader as unknown as ParserType; 191 | if (typeof parser.parse === 'function') { 192 | this.assetLoader.loader.instance = parser; 193 | } else { 194 | throw new Error('Provide loader has now parse method! Aborting...'); 195 | } 196 | this.assetLoader.loader.config = loaderConfig ?? {}; 197 | return this; 198 | } 199 | 200 | init() { 201 | console.log(this.name + ': Performing init'); 202 | applyProperties(this.assetLoader.loader.instance, this.assetLoader.loader.config, false); 203 | } 204 | 205 | async loadResource() { 206 | const fileLoader = new FileLoader(); 207 | fileLoader.setResponseType('arraybuffer'); 208 | if (this.resourceDescriptor) { 209 | const buffer = await fileLoader.loadAsync(this.resourceDescriptor.getUrl().href) as ArrayBufferLike; 210 | this.resourceDescriptor.setBuffer(buffer); 211 | return Promise.resolve(this.resourceDescriptor); 212 | } else { 213 | return Promise.reject(); 214 | } 215 | } 216 | 217 | process() { 218 | if (this.assetLoader.linker) { 219 | const resultBefore = this.relations.before?.processResult; 220 | const nextTask = this.relations.after?.assetLoader.loader.instance; 221 | if (resultBefore) { 222 | this.processResult = this.assetLoader.linker.link(resultBefore as unknown as AssociatedArrayType, nextTask!); 223 | } 224 | } else if (this.assetLoader.loader.instance) { 225 | if (this.resourceDescriptor?.isNeedStringOutput()) { 226 | const dataAsString = this.resourceDescriptor.getBufferAsString(); 227 | this.processResult = this.assetLoader.loader.instance.parse(dataAsString); 228 | } else { 229 | const dataAsBuffer = this.resourceDescriptor?.getBuffer() as ArrayBufferLike; 230 | this.processResult = this.assetLoader.loader.instance.parse(dataAsBuffer); 231 | } 232 | } 233 | } 234 | } 235 | 236 | export { 237 | AssetPipelineLoader, 238 | AssetPipeline, 239 | AssetTask 240 | }; 241 | -------------------------------------------------------------------------------- /packages/objloader2/src/MtlObjBridge.ts: -------------------------------------------------------------------------------- 1 | import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; 2 | import { AssociatedArrayType } from 'wtd-core'; 3 | import { LinkType } from './AssetPipelineLoader.js'; 4 | 5 | class MtlObjBridge implements LinkType { 6 | 7 | link(processResult: AssociatedArrayType, assetLoader: AssociatedArrayType) { 8 | if (typeof assetLoader.setMaterials === 'function') { 9 | assetLoader.setMaterials(MtlObjBridge.addMaterialsFromMtlLoader(processResult as unknown as MTLLoader.MaterialCreator)); 10 | } 11 | return undefined; 12 | } 13 | 14 | /** 15 | * Returns the array instance of {@link Material}. 16 | * 17 | * @param {MTLLoader.MaterialCreator} materialCreator instance of MTLLoader 18 | */ 19 | static addMaterialsFromMtlLoader(materialCreator: MTLLoader.MaterialCreator) { 20 | materialCreator.preload(); 21 | return materialCreator.materials; 22 | } 23 | } 24 | 25 | export { MtlObjBridge }; 26 | -------------------------------------------------------------------------------- /packages/objloader2/src/OBJLoader2.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FileLoader, 3 | Loader, 4 | LineSegments, 5 | Points, 6 | // Parser only 7 | Object3D, 8 | Mesh, 9 | BufferGeometry, 10 | BufferAttribute, 11 | Material, 12 | LoadingManager 13 | } from 'three'; 14 | import { 15 | AssociatedMaterialArrayType, 16 | MaterialStore, 17 | MaterialUtils 18 | } from 'wtd-three-ext'; 19 | import { 20 | MaterialMetaInfoType, 21 | OBJLoader2Parser, 22 | PreparedMeshType 23 | } from './OBJLoader2Parser.js'; 24 | 25 | export type CallbackOnLoadType = ((data: Object3D) => void); 26 | export type CallbackOnProgressMessageType = ((progressMessage: string) => void); 27 | export type CallbackOnErrorMessageType = ((error: Error) => void); 28 | export type CallbackOnMeshAlterType = ((mesh: Mesh | LineSegments | Points, data: Object3D) => void); 29 | 30 | export type CallbacksType = { 31 | onLoad?: CallbackOnLoadType; 32 | onError?: CallbackOnErrorMessageType; 33 | onProgress?: CallbackOnProgressMessageType; 34 | onMeshAlter?: CallbackOnMeshAlterType; 35 | } 36 | 37 | export type FileLoaderOnLoadType = (response: string | ArrayBuffer) => void; 38 | export type FileLoaderOnProgressType = (request: ProgressEvent) => void; 39 | export type FileLoaderOnErrorType = (event: ErrorEvent | unknown) => void; 40 | 41 | /** 42 | * Creates a new OBJLoader2. Use it to load OBJ data from files or to parse OBJ data from arraybuffer or text. 43 | * 44 | * @param {LoadingManager} [manager] The loadingManager for the loader to use. Default is {@link LoadingManager} 45 | * @constructor 46 | */ 47 | export class OBJLoader2 extends Loader { 48 | 49 | static OBJLOADER2_VERSION = '6.0.0'; 50 | 51 | protected parser = new OBJLoader2Parser(); 52 | protected baseObject3d = new Object3D(); 53 | protected materialStore = new MaterialStore(true); 54 | protected materialPerSmoothingGroup = false; 55 | protected useOAsMesh = false; 56 | protected useIndices = false; 57 | protected disregardNormals = false; 58 | protected modelName = 'noname'; 59 | 60 | private callbacks: CallbacksType; 61 | 62 | /** 63 | * 64 | * @param {LoadingManager} [manager] 65 | */ 66 | constructor(manager?: LoadingManager) { 67 | super(manager); 68 | this.callbacks = { 69 | onLoad: undefined, 70 | onError: undefined, 71 | onProgress: undefined, 72 | onMeshAlter: undefined 73 | }; 74 | } 75 | 76 | /** 77 | * Enable or disable logging in general (except warn and error), plus enable or disable debug logging. 78 | * 79 | * @param {boolean} enabled True or false. 80 | * @param {boolean} debug True or false. 81 | * 82 | * @return {OBJLoader2} 83 | */ 84 | setLogging(enabled: boolean, debug: boolean) { 85 | this.parser.setLogging(enabled, debug); 86 | return this; 87 | } 88 | 89 | /** 90 | * Tells whether a material shall be created per smoothing group. 91 | * 92 | * @param {boolean} materialPerSmoothingGroup=false 93 | * @return {OBJLoader2} 94 | */ 95 | setMaterialPerSmoothingGroup(materialPerSmoothingGroup: boolean) { 96 | this.materialPerSmoothingGroup = materialPerSmoothingGroup === true; 97 | return this; 98 | } 99 | 100 | /** 101 | * Usually 'o' is meta-information and does not result in creation of new meshes, but mesh creation on occurrence of "o" can be enforced. 102 | * 103 | * @param {boolean} useOAsMesh=false 104 | * @return {OBJLoader2} 105 | */ 106 | setUseOAsMesh(useOAsMesh: boolean) { 107 | this.useOAsMesh = useOAsMesh === true; 108 | return this; 109 | } 110 | 111 | /** 112 | * Instructs loaders to create indexed {@link BufferGeometry}. 113 | * 114 | * @param {boolean} useIndices=false 115 | * @return {OBJLoader2} 116 | */ 117 | setUseIndices(useIndices: boolean) { 118 | this.useIndices = useIndices === true; 119 | return this; 120 | } 121 | 122 | /** 123 | * Tells whether normals should be completely disregarded and regenerated. 124 | * 125 | * @param {boolean} disregardNormals=false 126 | * @return {OBJLoader2} 127 | */ 128 | setDisregardNormals(disregardNormals: boolean) { 129 | this.disregardNormals = disregardNormals === true; 130 | return this; 131 | } 132 | 133 | /** 134 | * Set the name of the model. 135 | * 136 | * @param {string} modelName 137 | * @return {OBJLoader2} 138 | */ 139 | setModelName(modelName: string) { 140 | if (modelName.length > 0) { 141 | this.modelName = modelName; 142 | } 143 | return this; 144 | } 145 | 146 | /** 147 | * Returns the name of the models 148 | * @return {String} 149 | */ 150 | getModelName() { 151 | return this.modelName; 152 | } 153 | 154 | /** 155 | * Set the node where the loaded objects will be attached directly. 156 | * 157 | * @param {Object3D} baseObject3d Object already attached to scenegraph where new meshes will be attached to 158 | * @return {OBJLoader2} 159 | */ 160 | setBaseObject3d(baseObject3d: Object3D) { 161 | this.baseObject3d = baseObject3d; 162 | return this; 163 | } 164 | 165 | /** 166 | * Clears materials object and sets the new ones. 167 | * 168 | * @param {Object} materials Object with named materials 169 | * @return {OBJLoader2} 170 | */ 171 | setMaterials(materials: AssociatedMaterialArrayType) { 172 | this.materialStore.addMaterialsFromObject(materials, false); 173 | return this; 174 | } 175 | 176 | /** 177 | * Register a function that is called when parsing was completed. 178 | * 179 | * @param {CallbackOnLoadType} onLoad 180 | * @return {OBJLoader2} 181 | */ 182 | setCallbackOnLoad(onLoad: CallbackOnLoadType) { 183 | this.callbacks.onLoad = onLoad; 184 | return this; 185 | } 186 | 187 | /** 188 | * Register a function that is used to report overall processing progress. 189 | * 190 | * @param {CallbackOnProgressMessageType} onProgress 191 | * @return {OBJLoader2} 192 | */ 193 | setCallbackOnProgress(onProgress: CallbackOnProgressMessageType) { 194 | this.callbacks.onProgress = onProgress; 195 | return this; 196 | } 197 | 198 | /** 199 | * Register an error handler function that is called if errors occur. It can decide to just log or to throw an exception. 200 | * 201 | * @param {CallbackOnErrorMessageType} onError 202 | * @return {OBJLoader2} 203 | */ 204 | setCallbackOnError(onError: CallbackOnErrorMessageType) { 205 | this.callbacks.onError = onError; 206 | return this; 207 | } 208 | 209 | /** 210 | * Register a function that is called once a single mesh is available and it could be altered by the supplied function. 211 | * 212 | * @param {CallbackOnMeshAlterType} onMeshAlter 213 | * @return {OBJLoader2} 214 | */ 215 | setCallbackOnMeshAlter(onMeshAlter: CallbackOnMeshAlterType) { 216 | this.callbacks.onMeshAlter = onMeshAlter; 217 | return this; 218 | } 219 | 220 | /** 221 | * Use this convenient method to load a file at the given URL. By default the fileLoader uses an ArrayBuffer. 222 | * 223 | * @param {string} url A string containing the path/URL of the file to be loaded. 224 | * @param {FileLoaderOnLoadType} [onLoad] A function to be called after loading is successfully completed. The function receives loaded Object3D as an argument. 225 | * @param {FileLoaderOnProgressType} [onProgress] A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains total and Integer bytes. 226 | * @param {FileLoaderOnErrorType} [onError] A function to be called if an error occurs during loading. The function receives the error as an argument. 227 | * @param {OnMeshAlterType} [onMeshAlter] Called after every single mesh is made available by the parser 228 | */ 229 | load(url: string | undefined, onLoad: CallbackOnLoadType, onProgress?: FileLoaderOnProgressType, onError?: FileLoaderOnErrorType, onMeshAlter?: CallbackOnMeshAlterType) { 230 | if (!(onLoad instanceof Function)) { 231 | const badOnLoadError = new Error('onLoad is not a function! Aborting...'); 232 | this._onError(badOnLoadError); 233 | throw badOnLoadError; 234 | } 235 | else { 236 | this.setCallbackOnLoad(onLoad); 237 | } 238 | 239 | if (!onError || !(onError instanceof Function)) { 240 | onError = (errorEvent: ErrorEvent | unknown) => { 241 | if (Object.hasOwn(errorEvent as ErrorEvent, 'currentTarget')) { 242 | const errorMessage = 'Error occurred while downloading!\nurl: ' + (errorEvent as ErrorEvent).currentTarget; 243 | this._onError(new Error(errorMessage)); 244 | } 245 | }; 246 | } 247 | 248 | if (url === undefined) { 249 | onError(new ErrorEvent('An invalid url was provided. Unable to continue!')); 250 | } 251 | let urlFull = ''; 252 | try { 253 | urlFull = new URL(url!).href; 254 | } catch (error) { 255 | urlFull = new URL(url!, window.location.href).href; 256 | } 257 | let filename = urlFull; 258 | const urlParts = urlFull.split('/'); 259 | if (urlParts.length > 2) { 260 | filename = urlParts[urlParts.length - 1]; 261 | const urlPartsPath = urlParts.slice(0, urlParts.length - 1).join('/') + '/'; 262 | if (urlPartsPath !== undefined) this.path = urlPartsPath; 263 | } 264 | if (!onProgress || !(onProgress instanceof Function)) { 265 | let numericalValueRef = 0; 266 | let numericalValue = 0; 267 | onProgress = (event) => { 268 | if (!event.lengthComputable) return; 269 | 270 | numericalValue = event.loaded / event.total; 271 | if (numericalValue > numericalValueRef) { 272 | numericalValueRef = numericalValue; 273 | const output = `Download of "${url}": ${(numericalValue * 100).toFixed(2)}%`; 274 | this._onProgress(output); 275 | } 276 | }; 277 | } 278 | 279 | if (onMeshAlter) { 280 | this.setCallbackOnMeshAlter(onMeshAlter); 281 | } 282 | 283 | const fileLoaderOnLoad = (content: string | ArrayBuffer) => { 284 | this.parse(content); 285 | }; 286 | const fileLoader = new FileLoader(this.manager); 287 | fileLoader.setPath(this.path || this.resourcePath); 288 | fileLoader.setResponseType('arraybuffer'); 289 | fileLoader.load(filename, fileLoaderOnLoad, onProgress, onError); 290 | } 291 | 292 | /** 293 | * Overrides the implementation of THREE.Loader, so it supports onMeshAlter. 294 | * 295 | * @param {string} url A string containing the path/URL of the file to be loaded. 296 | * @param {FileLoaderOnProgressType} [onProgress] A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains total and Integer bytes. 297 | * @param {CallbackOnMeshAlterType} [onMeshAlter] Called after every single mesh is made available by the parser url 298 | * @returns Promise 299 | */ 300 | loadAsync(url: string, onProgress?: FileLoaderOnProgressType, onMeshAlter?: CallbackOnMeshAlterType): Promise { 301 | return new Promise((resolve, reject) => { 302 | this.load(url, resolve, onProgress, reject, onMeshAlter); 303 | }); 304 | } 305 | 306 | /** 307 | * Parses OBJ data synchronously from arraybuffer or string and returns the {@link Object3D}. 308 | * 309 | * @param {ArrayBuffer|string} content OBJ data as Uint8Array or String 310 | * @return {Object3D} 311 | */ 312 | parse(objToParse: string | ArrayBuffer) { 313 | if (this.parser.isLoggingEnabled()) { 314 | console.info('Using OBJLoader2 version: ' + OBJLoader2.OBJLOADER2_VERSION); 315 | console.time('OBJLoader parse: ' + this.modelName); 316 | } 317 | 318 | if (objToParse instanceof ArrayBuffer) { 319 | if (this.parser.isLoggingEnabled()) { 320 | console.info('Parsing arrayBuffer...'); 321 | } 322 | this.configure(); 323 | this.parser.execute(objToParse as ArrayBufferLike); 324 | } 325 | else if (typeof (objToParse) === 'string') { 326 | if (this.parser.isLoggingEnabled()) { 327 | console.info('Parsing text...'); 328 | } 329 | this.configure(); 330 | this.parser.executeLegacy(objToParse); 331 | } 332 | else { 333 | this._onError(new Error('Provided objToParse was neither of type String nor Uint8Array! Aborting...')); 334 | } 335 | if (this.parser.isLoggingEnabled()) { 336 | console.timeEnd('OBJLoader parse: ' + this.modelName); 337 | } 338 | return this.baseObject3d; 339 | } 340 | 341 | private configure() { 342 | this.parser.setBulkConfig({ 343 | materialPerSmoothingGroup: this.materialPerSmoothingGroup, 344 | useOAsMesh: this.useOAsMesh, 345 | useIndices: this.useIndices, 346 | disregardNormals: this.disregardNormals, 347 | modelName: this.modelName, 348 | materialNames: new Set(Array.from(this.materialStore.getMaterials().keys())) 349 | }); 350 | 351 | this.parser._onAssetAvailable = (preparedMesh: PreparedMeshType) => { 352 | const mesh = OBJLoader2.buildThreeMesh(preparedMesh, this.materialStore.getMaterials(), this.parser.isDebugLoggingEnabled()); 353 | if (mesh) { 354 | this._onMeshAlter(mesh, preparedMesh.materialMetaInfo); 355 | this.baseObject3d.add(mesh); 356 | } 357 | }; 358 | this.parser._onLoad = () => { 359 | this._onLoad(); 360 | }; 361 | this.printCallbackConfig(); 362 | } 363 | 364 | protected printCallbackConfig() { 365 | if (this.parser.isLoggingEnabled()) { 366 | let printedConfig = 'OBJLoader2 callback configuration:'; 367 | if (this.callbacks.onProgress !== null) { 368 | printedConfig += `\n\tcallbacks.onProgress: ${this.callbacks.onProgress?.name ?? undefined}`; 369 | } 370 | if (this.callbacks.onError !== null) { 371 | printedConfig += `\n\tcallbacks.onError: ${this.callbacks.onError?.name ?? undefined}`; 372 | } 373 | if (this.callbacks.onMeshAlter !== null) { 374 | printedConfig += `\n\tcallbacks.onMeshAlter: ${this.callbacks.onMeshAlter?.name ?? undefined}`; 375 | } 376 | if (this.callbacks.onLoad !== null) { 377 | printedConfig += `\n\tcallbacks.onLoad: ${this.callbacks.onLoad?.name ?? undefined}`; 378 | } 379 | console.info(printedConfig); 380 | } 381 | } 382 | 383 | static buildThreeMesh({ 384 | meshName: meshName, 385 | vertexFA: vertexFA, 386 | normalFA: normalFA, 387 | uvFA: uvFA, 388 | colorFA: colorFA, 389 | indexUA: indexUA, 390 | createMultiMaterial: createMultiMaterial, 391 | geometryGroups: geometryGroups, 392 | multiMaterial: multiMaterial, 393 | materialMetaInfo: materialMetaInfo 394 | }: PreparedMeshType, materials: Map, debugLogging: boolean): Mesh | LineSegments | Points { 395 | const geometry = new BufferGeometry(); 396 | geometry.setAttribute('position', new BufferAttribute(vertexFA, 3, false)); 397 | if (normalFA !== null) { 398 | geometry.setAttribute('normal', new BufferAttribute(normalFA, 3, false)); 399 | } 400 | if (uvFA !== null) { 401 | geometry.setAttribute('uv', new BufferAttribute(uvFA, 2, false)); 402 | } 403 | if (colorFA !== null) { 404 | geometry.setAttribute('color', new BufferAttribute(colorFA, 3, false)); 405 | } 406 | if (indexUA !== null) { 407 | geometry.setIndex(new BufferAttribute(indexUA, 1, false)); 408 | } 409 | 410 | if (geometryGroups.length > 0) { 411 | for (const geometryGroup of geometryGroups) { 412 | geometry.addGroup(geometryGroup.materialGroupOffset, geometryGroup.materialGroupLength, geometryGroup.materialIndex); 413 | } 414 | } 415 | 416 | // compute missing vertex normals only after indices have been added! 417 | if (normalFA === null) { 418 | geometry.computeVertexNormals(); 419 | } 420 | 421 | let material; 422 | if (materialMetaInfo.materialCloneInstructions.length > 0) { 423 | for (const materialCloneInstruction of materialMetaInfo.materialCloneInstructions) { 424 | material = MaterialUtils.cloneMaterial(materials, materialCloneInstruction, debugLogging); 425 | } 426 | } 427 | else { 428 | material = materials.get(materialMetaInfo.materialName); 429 | } 430 | 431 | const realMultiMaterials: Material[] = []; 432 | if (createMultiMaterial) { 433 | for (let i = 0; i < multiMaterial.length; i++) { 434 | const currentMultiMaterial = materials.get(multiMaterial[i]); 435 | if (currentMultiMaterial) { 436 | realMultiMaterials[i] = currentMultiMaterial; 437 | } 438 | } 439 | } 440 | 441 | let mesh; 442 | const appliedMaterial = createMultiMaterial ? realMultiMaterials : material; 443 | if (materialMetaInfo.geometryType === 0) { 444 | mesh = new Mesh(geometry, appliedMaterial); 445 | } 446 | else if (materialMetaInfo.geometryType === 1) { 447 | mesh = new LineSegments(geometry, appliedMaterial); 448 | } 449 | else { 450 | mesh = new Points(geometry, appliedMaterial); 451 | } 452 | if (mesh) { 453 | mesh.name = meshName; 454 | } 455 | return mesh; 456 | } 457 | 458 | _onProgress(text: string) { 459 | if (this.callbacks.onProgress) { 460 | this.callbacks.onProgress(text); 461 | } 462 | else { 463 | this.parser._onProgress(text); 464 | } 465 | } 466 | 467 | _onError(error: Error) { 468 | if (this.callbacks.onError) { 469 | this.callbacks.onError(error); 470 | } 471 | else { 472 | this.parser._onError(error.message); 473 | } 474 | 475 | } 476 | 477 | _onMeshAlter(mesh: Mesh | LineSegments | Points, _materialMetaInfo?: MaterialMetaInfoType) { 478 | if (this.callbacks.onMeshAlter) { 479 | this.callbacks.onMeshAlter(mesh, this.baseObject3d); 480 | } 481 | } 482 | 483 | _onLoad() { 484 | if (this.callbacks.onLoad) { 485 | this.callbacks.onLoad(this.baseObject3d); 486 | } 487 | } 488 | 489 | } 490 | -------------------------------------------------------------------------------- /packages/objloader2/src/OBJLoader2Parallel.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Object3D, 3 | LoadingManager 4 | } from 'three'; 5 | import { 6 | DataPayload, 7 | WorkerTask, 8 | WorkerTaskCommandResponse, 9 | WorkerTaskMessage 10 | } from 'wtd-core'; 11 | import { CallbackOnLoadType, CallbackOnMeshAlterType, FileLoaderOnErrorType, FileLoaderOnProgressType, OBJLoader2 } from './OBJLoader2.js'; 12 | import { PreparedMeshType } from './OBJLoader2Parser.js'; 13 | 14 | /** 15 | * Creates a new OBJLoader2Parallel. Use it to load OBJ data from files or to parse OBJ data from arraybuffer. 16 | * It extends {@link OBJLoader2} with the capability to run the parser in a web worker. 17 | * 18 | * @param [LoadingManager] manager The loadingManager for the loader to use. Default is {@link LoadingManager} 19 | * @constructor 20 | */ 21 | export class OBJLoader2Parallel extends OBJLoader2 { 22 | 23 | static OBJLOADER2_PARALLEL_VERSION = OBJLoader2.OBJLOADER2_VERSION; 24 | static DEFAULT_DEV_MODULE_WORKER_PATH = './worker/OBJLoader2Worker.js'; 25 | static DEFAULT_DEV_STANDARD_WORKER_PATH = '../lib/worker/OBJLoader2WorkerClassic.js'; 26 | static DEFAULT_PROD_MODULE_WORKER_PATH = './worker/OBJLoader2WorkerModule.js'; 27 | static DEFAULT_PROD_STANDARD_WORKER_PATH = './worker/OBJLoader2WorkerClassic.js'; 28 | static TASK_NAME = 'OBJLoader2Worker'; 29 | 30 | private moduleWorker = true; 31 | private workerUrl = OBJLoader2Parallel.getModuleWorkerDefaultUrl(); 32 | private terminateWorkerOnLoad = false; 33 | private workerTask?: WorkerTask; 34 | 35 | /** 36 | * 37 | * @param {LoadingManager} [manager] 38 | */ 39 | constructor(manager?: LoadingManager) { 40 | super(manager); 41 | } 42 | 43 | /** 44 | * Set whether jsm modules in workers should be used. This requires browser support 45 | * which is availableminall major browser apart from Firefox. 46 | * 47 | * @param {boolean} moduleWorker If the worker is a module or a standard worker 48 | * @param {URL} workerUrl Provide complete worker URL otherwise relative path to this module may not be correct 49 | * @return {OBJLoader2Parallel} 50 | */ 51 | setWorkerUrl(moduleWorker: boolean, workerUrl: URL) { 52 | this.moduleWorker = moduleWorker === true; 53 | this.workerUrl = workerUrl; 54 | return this; 55 | } 56 | 57 | static getModuleWorkerDefaultUrl() { 58 | return new URL(import.meta.env.DEV ? OBJLoader2Parallel.DEFAULT_DEV_MODULE_WORKER_PATH : OBJLoader2Parallel.DEFAULT_PROD_MODULE_WORKER_PATH, import.meta.url); 59 | } 60 | 61 | static getStandardWorkerDefaultUrl() { 62 | return new URL(import.meta.env.DEV ? OBJLoader2Parallel.DEFAULT_DEV_STANDARD_WORKER_PATH : OBJLoader2Parallel.DEFAULT_PROD_STANDARD_WORKER_PATH, import.meta.url); 63 | } 64 | 65 | /** 66 | * Request termination of worker once parser is finished. 67 | * 68 | * @param {boolean} terminateWorkerOnLoad True or false. 69 | * @return {OBJLoader2Parallel} 70 | */ 71 | setTerminateWorkerOnLoad(terminateWorkerOnLoad: boolean) { 72 | this.terminateWorkerOnLoad = terminateWorkerOnLoad === true; 73 | return this; 74 | } 75 | 76 | /** 77 | * See {@link OBJLoader2.load} 78 | */ 79 | load(url: string, onLoad: CallbackOnLoadType, onProgress?: FileLoaderOnProgressType, 80 | onError?: FileLoaderOnErrorType, onMeshAlter?: CallbackOnMeshAlterType) { 81 | const interceptOnLoad = (object3d: Object3D) => { 82 | if (object3d.name === 'OBJLoader2ParallelDummy') { 83 | if (this.parser.isDebugLoggingEnabled()) { 84 | console.debug('Received dummy answer from OBJLoader2Parallel#parse'); 85 | } 86 | } 87 | else { 88 | if (onLoad) { 89 | onLoad(object3d); 90 | } else { 91 | throw new Error('"onLoad" callback was not provided. Aborting...'); 92 | } 93 | } 94 | }; 95 | OBJLoader2.prototype.load.call(this, url, interceptOnLoad, onProgress, onError, onMeshAlter); 96 | } 97 | 98 | /** 99 | * See {@link OBJLoader2.parse} 100 | * The callback onLoad needs to be set to be able to receive the content if used in parallel mode. 101 | */ 102 | parse(objToParse: ArrayBuffer) { 103 | if (this.parser.isLoggingEnabled()) { 104 | console.info('Using OBJLoader2Parallel version: ' + OBJLoader2Parallel.OBJLOADER2_PARALLEL_VERSION); 105 | } 106 | 107 | this.printCallbackConfig(); 108 | this.initWorkerParse(objToParse); 109 | const dummy = new Object3D(); 110 | dummy.name = 'OBJLoader2ParallelDummy'; 111 | return dummy; 112 | } 113 | 114 | private async initWorkerParse(objToParse: ArrayBuffer) { 115 | this.workerTask = new WorkerTask({ 116 | taskName: OBJLoader2Parallel.TASK_NAME, 117 | workerId: 1, 118 | workerConfig: { 119 | $type: 'WorkerConfigParams', 120 | workerType: this.moduleWorker ? 'module' : 'classic', 121 | blob: false, 122 | url: this.workerUrl 123 | }, 124 | verbose: this.parser.isDebugLoggingEnabled() 125 | }); 126 | 127 | try { 128 | await this.initWorker(); 129 | if (this.parser.isDebugLoggingEnabled()) { 130 | console.log('OBJLoader2Parallel init was performed'); 131 | } 132 | this.executeWorker(objToParse); 133 | } catch (e) { 134 | console.error(e); 135 | } 136 | } 137 | 138 | /** 139 | * Provide instructions on what is to be contained in the worker. 140 | * 141 | * @return {Promise} 142 | * @private 143 | */ 144 | private initWorker() { 145 | const dataPayload = new DataPayload(); 146 | dataPayload.message.params = { 147 | logging: { 148 | enabled: this.parser.isLoggingEnabled(), 149 | debug: this.parser.isDebugLoggingEnabled() 150 | } 151 | }; 152 | 153 | this.workerTask!.connectWorker(); 154 | return this.workerTask!.initWorker({ message: WorkerTaskMessage.fromPayload(dataPayload) }); 155 | } 156 | 157 | private async executeWorker(objToParse: ArrayBuffer) { 158 | const execMessage = WorkerTaskMessage.createEmpty(); 159 | const dataPayload = new DataPayload(); 160 | dataPayload.message.params = { 161 | modelName: this.modelName, 162 | useIndices: this.useIndices, 163 | disregardNormals: this.disregardNormals, 164 | materialPerSmoothingGroup: this.materialPerSmoothingGroup, 165 | useOAsMesh: this.useOAsMesh, 166 | logging: { 167 | enabled: this.parser.isLoggingEnabled(), 168 | debug: this.parser.isDebugLoggingEnabled() 169 | } 170 | }; 171 | dataPayload.message.buffers?.set('modelData', objToParse); 172 | dataPayload.message.params.materialNames = new Set(Array.from(this.materialStore.getMaterials().keys())); 173 | 174 | execMessage.addPayload(dataPayload); 175 | const transferables = WorkerTaskMessage.pack(execMessage.payloads, false); 176 | 177 | try { 178 | await this.workerTask?.executeWorker({ 179 | message: execMessage, 180 | onIntermediateConfirm: (message) => { 181 | this.onWorkerMessage(message); 182 | }, 183 | onComplete: (message) => { 184 | this.onWorkerMessage(message); 185 | if (this.terminateWorkerOnLoad) { 186 | this.workerTask!.dispose(); 187 | } 188 | }, 189 | transferables: transferables 190 | }); 191 | console.log('Worker execution completed successfully.'); 192 | } catch (e) { 193 | console.error(e); 194 | } 195 | } 196 | 197 | /** 198 | * 199 | * @param {Mesh} mesh 200 | * @param {object} materialMetaInfo 201 | */ 202 | private onWorkerMessage(message: WorkerTaskMessage) { 203 | const wtm = WorkerTaskMessage.unpack(message, false); 204 | if (wtm.cmd === WorkerTaskCommandResponse.INTERMEDIATE_CONFIRM) { 205 | 206 | const dataPayload = (wtm.payloads.length === 1) ? wtm.payloads[0] as DataPayload : undefined; 207 | if (dataPayload && dataPayload.message.params) { 208 | const preparedMesh = dataPayload.message.params.preparedMesh as PreparedMeshType; 209 | const mesh = OBJLoader2.buildThreeMesh(preparedMesh, this.materialStore.getMaterials(), this.parser.isDebugLoggingEnabled()); 210 | if (mesh) { 211 | this._onMeshAlter(mesh, preparedMesh.materialMetaInfo); 212 | this.baseObject3d.add(mesh); 213 | } 214 | } 215 | else { 216 | console.error('Received intermediate message without a proper payload'); 217 | } 218 | } 219 | else if (wtm.cmd === WorkerTaskCommandResponse.EXECUTE_COMPLETE) { 220 | this._onLoad(); 221 | } 222 | else { 223 | console.error(`Received unknown command: ${wtm.cmd}`); 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /packages/objloader2/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OBJLoader2Parser 3 | } from './OBJLoader2Parser.js'; 4 | import type { 5 | BulkConfigType, 6 | GeometryGroupType, 7 | GlobalCountsType, 8 | LoggingType, 9 | MaterialCloneInstructionType, 10 | MaterialMetaInfoType, 11 | PreparedMeshType, 12 | RawMeshResultType, 13 | RawMeshSubGroupType, 14 | RawMeshType 15 | } from './OBJLoader2Parser.js'; 16 | 17 | import { 18 | OBJLoader2 19 | } from './OBJLoader2.js'; 20 | 21 | import type { 22 | CallbackOnErrorMessageType, 23 | CallbackOnLoadType, 24 | CallbackOnMeshAlterType, 25 | CallbackOnProgressMessageType, 26 | CallbacksType, 27 | FileLoaderOnErrorType, 28 | FileLoaderOnLoadType, 29 | FileLoaderOnProgressType 30 | } from './OBJLoader2.js'; 31 | 32 | import { 33 | OBJLoader2Parallel 34 | } from './OBJLoader2Parallel.js'; 35 | 36 | import { 37 | MtlObjBridge 38 | } from './MtlObjBridge.js'; 39 | 40 | import { 41 | ResourceDescriptor 42 | } from './utils/ResourceDescriptor.js'; 43 | 44 | import type { 45 | CallbackCompleteType, 46 | ParserType, 47 | LinkType 48 | } from './AssetPipelineLoader.js'; 49 | 50 | import { 51 | AssetPipelineLoader, 52 | AssetPipeline, 53 | AssetTask 54 | } from './AssetPipelineLoader.js'; 55 | 56 | export { 57 | OBJLoader2, 58 | OBJLoader2Parser, 59 | OBJLoader2Parallel, 60 | MtlObjBridge, 61 | ResourceDescriptor, 62 | AssetPipelineLoader, 63 | AssetPipeline, 64 | AssetTask 65 | }; 66 | 67 | export type { 68 | BulkConfigType, 69 | GeometryGroupType, 70 | GlobalCountsType, 71 | LoggingType, 72 | MaterialCloneInstructionType, 73 | MaterialMetaInfoType, 74 | PreparedMeshType, 75 | RawMeshResultType, 76 | RawMeshSubGroupType, 77 | RawMeshType, 78 | CallbackOnErrorMessageType, 79 | CallbackOnLoadType, 80 | CallbackOnMeshAlterType, 81 | CallbackOnProgressMessageType, 82 | CallbacksType, 83 | FileLoaderOnErrorType, 84 | FileLoaderOnLoadType, 85 | FileLoaderOnProgressType, 86 | CallbackCompleteType, 87 | ParserType, 88 | LinkType 89 | }; 90 | -------------------------------------------------------------------------------- /packages/objloader2/src/utils/ResourceDescriptor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Encapsulates a url and derived values (filename, extension and path and stores the {@link ArrayBuffer} 3 | * loaded from the resource described by the url. 4 | */ 5 | class ResourceDescriptor { 6 | 7 | private url; 8 | private path = './'; 9 | private filename: string; 10 | private extension: string | undefined; 11 | private buffer: ArrayBufferLike | undefined; 12 | private needStringOutput = false; 13 | 14 | constructor(url: string) { 15 | this.url = new URL(url, window.location.href); 16 | this.filename = url; 17 | 18 | const urlParts = this.url.href.split('/'); 19 | if (urlParts.length > 2) { 20 | this.filename = urlParts[urlParts.length - 1]; 21 | const urlPartsPath = urlParts.slice(0, urlParts.length - 1).join('/') + '/'; 22 | if (urlPartsPath !== undefined) this.path = urlPartsPath; 23 | } 24 | const filenameParts = this.filename.split('.'); 25 | if (filenameParts.length > 1) { 26 | this.extension = filenameParts[filenameParts.length - 1]; 27 | } 28 | } 29 | 30 | getUrl() { 31 | return this.url; 32 | } 33 | 34 | /** 35 | * Returns ths path from the base of the URL to the file 36 | * @return {string} 37 | */ 38 | getPath() { 39 | return this.path; 40 | } 41 | 42 | getFilename() { 43 | return this.filename; 44 | } 45 | 46 | getExtension() { 47 | return this.extension; 48 | } 49 | 50 | setNeedStringOutput(needStringOutput: boolean) { 51 | this.needStringOutput = needStringOutput; 52 | return this; 53 | } 54 | 55 | isNeedStringOutput() { 56 | return this.needStringOutput; 57 | } 58 | 59 | /** 60 | * Set the buffer after loading. 61 | * @param {ArrayBufferLike} buffer 62 | * @return {ResourceDescriptor} 63 | */ 64 | setBuffer(buffer: ArrayBufferLike) { 65 | if (!(buffer instanceof ArrayBuffer || 66 | buffer instanceof Int8Array || 67 | buffer instanceof Uint8Array || 68 | buffer instanceof Uint8ClampedArray || 69 | buffer instanceof Int16Array || 70 | buffer instanceof Uint16Array || 71 | buffer instanceof Int32Array || 72 | buffer instanceof Uint32Array || 73 | buffer instanceof Float32Array || 74 | buffer instanceof Float64Array)) { 75 | throw (new Error('Provided input is neither an "ArrayBuffer" nor a "TypedArray"! Aborting...')); 76 | } else { 77 | this.buffer = buffer; 78 | } 79 | return this; 80 | } 81 | 82 | getBuffer() { 83 | return this.buffer; 84 | } 85 | 86 | /** 87 | * Returns the buffer as string by using {@link TextDecoder}. 88 | * 89 | * @return {string} 90 | */ 91 | getBufferAsString() { 92 | return this.buffer ? new TextDecoder('utf-8').decode(this.buffer) : ''; 93 | } 94 | 95 | } 96 | 97 | export { ResourceDescriptor }; 98 | -------------------------------------------------------------------------------- /packages/objloader2/src/worker/OBJLoader2Worker.ts: -------------------------------------------------------------------------------- 1 | import { 2 | applyProperties, 3 | comRouting, 4 | AssociatedArrayType, 5 | DataPayload, 6 | WorkerTaskCommandResponse, 7 | WorkerTaskMessage, 8 | WorkerTaskWorker 9 | } from 'wtd-core'; 10 | import { 11 | OBJLoader2Parser 12 | } from '../OBJLoader2Parser.js'; 13 | 14 | type LocalData = { 15 | debugLogging: boolean; 16 | params: AssociatedArrayType; 17 | buffer?: ArrayBufferLike; 18 | materialNames: Set; 19 | } 20 | 21 | class OBJLoader2Worker implements WorkerTaskWorker { 22 | 23 | private localData: LocalData = { 24 | params: {}, 25 | debugLogging: false, 26 | materialNames: new Set() 27 | }; 28 | 29 | initParser(wtm: WorkerTaskMessage) { 30 | const parser = new OBJLoader2Parser(); 31 | parser._onAssetAvailable = preparedMesh => { 32 | const dataPayload = new DataPayload(); 33 | if (!dataPayload.message.params) { 34 | dataPayload.message.params = {}; 35 | } 36 | dataPayload.message.params.preparedMesh = preparedMesh; 37 | if (preparedMesh.vertexFA !== null) { 38 | dataPayload.message.buffers?.set('vertexFA', preparedMesh.vertexFA); 39 | } 40 | if (preparedMesh.normalFA !== null) { 41 | dataPayload.message.buffers?.set('normalFA', preparedMesh.normalFA); 42 | } 43 | if (preparedMesh.uvFA !== null) { 44 | dataPayload.message.buffers?.set('uvFA', preparedMesh.uvFA); 45 | } 46 | if (preparedMesh.colorFA !== null) { 47 | dataPayload.message.buffers?.set('colorFA', preparedMesh.colorFA); 48 | } 49 | if (preparedMesh.indexUA !== null) { 50 | dataPayload.message.buffers?.set('indexUA', preparedMesh.indexUA); 51 | } 52 | 53 | const intermediateMessage = WorkerTaskMessage.createFromExisting(wtm, { 54 | overrideCmd: WorkerTaskCommandResponse.INTERMEDIATE_CONFIRM, 55 | }); 56 | intermediateMessage.addPayload(dataPayload); 57 | intermediateMessage.progress = preparedMesh.progress; 58 | 59 | const transferables = WorkerTaskMessage.pack(intermediateMessage.payloads, false); 60 | self.postMessage(intermediateMessage, transferables); 61 | }; 62 | 63 | parser._onLoad = () => { 64 | const execMessage = WorkerTaskMessage.createFromExisting(wtm, { 65 | overrideCmd: WorkerTaskCommandResponse.EXECUTE_COMPLETE 66 | }); 67 | // no packing required as no Transferables here 68 | self.postMessage(execMessage); 69 | }; 70 | 71 | parser._onProgress = text => { 72 | if (parser?.isDebugLoggingEnabled()) { 73 | console.debug('WorkerRunner: progress: ' + text); 74 | } 75 | }; 76 | 77 | return parser; 78 | } 79 | 80 | init(message: WorkerTaskMessage) { 81 | const wtm = this.processMessage(message); 82 | 83 | if (this.localData.debugLogging) { 84 | console.log(`OBJLoader2Worker#init: name: ${message.name} id: ${message.uuid} cmd: ${message.cmd} workerId: ${message.workerId}`); 85 | } 86 | 87 | const initComplete = WorkerTaskMessage.createFromExisting(wtm, { 88 | overrideCmd: WorkerTaskCommandResponse.INIT_COMPLETE 89 | }); 90 | self.postMessage(initComplete); 91 | } 92 | 93 | execute(message: WorkerTaskMessage) { 94 | this.processMessage(message); 95 | 96 | const parser = this.initParser(message); 97 | 98 | // apply previously stored parameters (init or execute) 99 | applyProperties(parser, this.localData.params, false); 100 | if (this.localData.materialNames) { 101 | parser?.setMaterialNames(this.localData.materialNames); 102 | } 103 | 104 | if (parser.isDebugLoggingEnabled()) { 105 | console.log(`OBJLoader2Worker#execute: name: ${message.name} id: ${message.uuid} cmd: ${message.cmd} workerId: ${message.workerId}`); 106 | } 107 | 108 | if (this.localData.buffer) { 109 | parser.execute(this.localData.buffer); 110 | } 111 | else { 112 | self.postMessage(new Error('No ArrayBuffer was provided for parsing.')); 113 | } 114 | } 115 | 116 | private processMessage(message: WorkerTaskMessage) { 117 | const wtm = WorkerTaskMessage.unpack(message, false); 118 | const dataPayload = wtm.payloads[0] as DataPayload; 119 | 120 | applyProperties(this.localData.params, dataPayload.message.params, true); 121 | const modelData = dataPayload.message.buffers?.get('modelData'); 122 | if (modelData) { 123 | this.localData.buffer = modelData; 124 | } 125 | 126 | if (dataPayload.message.params) { 127 | if (dataPayload.message.params.materialNames) { 128 | this.localData.materialNames = dataPayload.message.params.materialNames as Set; 129 | } 130 | } 131 | 132 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 133 | const logging = this.localData.params?.logging as any ?? {}; 134 | if (Object.hasOwn(logging, 'enabled') && Object.hasOwn(logging, 'debug')) { 135 | this.localData.debugLogging = logging.enabled === true && logging.debug === true; 136 | } 137 | return wtm; 138 | } 139 | } 140 | 141 | const worker = new OBJLoader2Worker(); 142 | self.onmessage = message => comRouting(worker, message); 143 | -------------------------------------------------------------------------------- /packages/objloader2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist", 6 | "declarationDir": "dist" 7 | }, 8 | "include": [ 9 | "src/**/*" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/objloader2/vite.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { defineConfig } from 'vite'; 3 | import path from 'path'; 4 | 5 | export default defineConfig({ 6 | build: { 7 | emptyOutDir: true, 8 | rollupOptions: { 9 | external: ['three'] 10 | }, 11 | outDir: 'lib', 12 | lib: { 13 | entry: path.resolve(__dirname, 'src/index.ts'), 14 | name: 'objloader2', 15 | fileName: 'objloader2', 16 | formats: ['es'] 17 | } 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /packages/objloader2/vite.config.worker.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { defineConfig } from 'vite'; 3 | 4 | const config = defineConfig({ 5 | build: { 6 | lib: { 7 | entry: path.resolve(__dirname, 'src/worker/OBJLoader2Worker.ts'), 8 | name: 'OBJLoader2WorkerStandard', 9 | fileName: (format) => format === 'iife' ? 'OBJLoader2WorkerClassic.js' : 'OBJLoader2WorkerModule.js', 10 | formats: ['iife', 'es'], 11 | }, 12 | outDir: 'lib/worker', 13 | emptyOutDir: false 14 | } 15 | }); 16 | 17 | export default config; 18 | -------------------------------------------------------------------------------- /scripts/updateVersions.mts: -------------------------------------------------------------------------------- 1 | import shell from 'shelljs'; 2 | 3 | const versionPattern = '.*[0-9]\\.[0-9]\\.[0-9].*'; 4 | 5 | const version_wtd_core_real = '~3.0.0'; 6 | const version_wtd_core_dev = '../../../wtd/packages/wtd-core'; 7 | 8 | const version_wtd_three_ext_real = '~3.0.0'; 9 | const version_wtd_three_ext_dev = '../../../wtd/packages/wtd-three-ext'; 10 | 11 | const what = process.argv[2]; 12 | 13 | const setDevVersions = () => { 14 | console.log('Updating to dev versions...'); 15 | shell.sed('-i', `"wtd-core": "${versionPattern}"`, `"wtd-core": "${version_wtd_core_dev}"`, 'packages/objloader2/package.json'); 16 | shell.sed('-i', `"wtd-three-ext": "${versionPattern}"`, `"wtd-three-ext": "${version_wtd_three_ext_dev}"`, 'packages/objloader2/package.json'); 17 | shell.sed('-i', `"wtd-core": "${versionPattern}"`, `"wtd-core": "${version_wtd_core_dev}"`, 'packages/examples/package.json'); 18 | shell.sed('-i', `"wtd-three-ext": "${versionPattern}"`, `"wtd-three-ext": "${version_wtd_three_ext_dev}"`, 'packages/examples/package.json'); 19 | }; 20 | 21 | const setRealVersions = () => { 22 | console.log('Updating to real versions...'); 23 | shell.sed('-i', `"wtd-core": "${version_wtd_core_dev}"`, `"wtd-core": "${version_wtd_core_real}"`, 'packages/objloader2/package.json'); 24 | shell.sed('-i', `"wtd-three-ext": "${version_wtd_three_ext_dev}"`, `"wtd-three-ext": "${version_wtd_three_ext_real}"`, 'packages/objloader2/package.json'); 25 | shell.sed('-i', `"wtd-core": "${version_wtd_core_dev}"`, `"wtd-core": "${version_wtd_core_real}"`, 'packages/examples/package.json'); 26 | shell.sed('-i', `"wtd-three-ext": "${version_wtd_three_ext_dev}"`, `"wtd-three-ext": "${version_wtd_three_ext_real}"`, 'packages/examples/package.json'); 27 | }; 28 | 29 | const reinstall = () => { 30 | console.log('Preforming reinstall...'); 31 | shell.rm('package-lock.json'); 32 | shell.rm('-fr', 'node_modules/wtd-core'); 33 | shell.rm('-fr', 'node_modules/wtd-three-ext'); 34 | shell.rm('-fr', 'packages/objloader2/node_modules'); 35 | shell.rm('-fr', 'packages/examples/node_modules'); 36 | 37 | shell.exec('npm i'); 38 | shell.exec('npm ci'); 39 | }; 40 | 41 | const fullReinstall = () => { 42 | console.log('Preforming reinstall...'); 43 | shell.rm('package-lock.json'); 44 | shell.rm('-fr', 'node_modules'); 45 | shell.rm('-fr', 'packages/objloader2/node_modules'); 46 | shell.rm('-fr', 'packages/examples/node_modules'); 47 | }; 48 | 49 | if (what === 'dev') { 50 | setDevVersions(); 51 | reinstall(); 52 | } 53 | 54 | if (what === 'real') { 55 | setRealVersions(); 56 | reinstall(); 57 | } 58 | 59 | if (what === 'reinstall') { 60 | fullReinstall(); 61 | } 62 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./packages/objloader2/tsconfig.json" }, 5 | { "path": "./packages/examples/tsconfig.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "lib": [ 7 | "ES2022", 8 | "DOM", 9 | "WebWorker" 10 | ], 11 | "types": [ 12 | "vite/client" 13 | ], 14 | "useDefineForClassFields": true, 15 | "strict": true, 16 | "composite": true, 17 | "sourceMap": true, 18 | "allowJs": true, 19 | "resolveJsonModule": true, 20 | "esModuleInterop": true, 21 | "noEmit": false, 22 | "declaration": true, 23 | "declarationMap": true, 24 | "noUnusedLocals": true, 25 | "noUnusedParameters": true, 26 | "noImplicitReturns": true, 27 | "skipLibCheck": true, 28 | "jsx": "react" 29 | }, 30 | "include": [ 31 | "**/src/**/*.ts", 32 | "**/test/**/*.ts" 33 | ], 34 | "exclude": [ 35 | "**/node_modules/**/*", 36 | "**/lib/**/*", 37 | "**/dist/**/*" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { defineConfig } from 'vite'; 3 | import path from 'path'; 4 | 5 | // Config Hints: 6 | // https://vitejs.dev/guide/build.html#multi-page-app 7 | 8 | export default defineConfig(({ command }) => { 9 | console.log(`Running: ${command}`); 10 | return { 11 | build: { 12 | rollupOptions: { 13 | external: [ 14 | 'three', 15 | 'three/examples/jsm/controls/TrackballControls', 16 | 'three/examples/jsm/helpers/VertexNormalsHelper', 17 | 'three/examples/jsm/loaders/MTLLoader' 18 | ], 19 | input: { 20 | index: path.resolve(__dirname, 'index.html'), 21 | obj2_basic: path.resolve(__dirname, 'packages/examples/obj2_basic.html'), 22 | obj2parallel_basic: path.resolve(__dirname, 'packages/examples/obj2parallel_basic.html'), 23 | obj2_options: path.resolve(__dirname, 'packages/examples/obj2_options.html'), 24 | obj2_obj_compare: path.resolve(__dirname, 'packages/examples/obj2_obj_compare.html'), 25 | obj2_bugverify: path.resolve(__dirname, 'packages/examples/obj2_bugverify.html'), 26 | obj2_react: path.resolve(__dirname, 'packages/examples/obj2_react.html'), 27 | obj2_react_mtl: path.resolve(__dirname, 'packages/examples/obj2_react-mtl.html'), 28 | assetpipeline: path.resolve(__dirname, 'packages/examples/assetpipeline.html'), 29 | assetpipeline_obj_stage: path.resolve(__dirname, 'packages/examples/assetpipeline_obj_stage.html') 30 | } 31 | } 32 | }, 33 | server: { 34 | port: 8085, 35 | host: '0.0.0.0' 36 | }, 37 | test: { 38 | passWithNoTests: true 39 | } 40 | }; 41 | }); 42 | --------------------------------------------------------------------------------