Compare commits

...

48 Commits

Author SHA1 Message Date
Alexander Rose
4d0f0ceebf 3.0.0-dev.3 2021-12-04 22:23:51 -08:00
Alexander Rose
14abcddfcf changelog 2021-12-04 22:17:24 -08:00
Alexander Rose
704cc96a9f Merge pull request #300 from sukolsak/fix-export
Fix USDZ and OBJ export
2021-12-04 22:13:41 -08:00
Sukolsak Sakshuwong
b51f610173 fix USDZ and OBJ export 2021-12-04 20:08:15 -08:00
dsehnal
470280ea1a changelog and update eslint 2021-12-01 13:29:02 +01:00
dsehnal
dafb5a8299 3.0.0-dev.2 2021-12-01 13:26:08 +01:00
dsehnal
4a1af03744 package.json fix 2021-12-01 13:23:50 +01:00
dsehnal
bb176f1efb 3.0.0-dev.1 2021-12-01 13:17:15 +01:00
dsehnal
a53bcde973 npmignore tweaks 2021-12-01 13:14:28 +01:00
David Sehnal
1a8dc2c637 Merge pull request #295 from molstar/material-object
Refactor Material Representation
2021-11-29 18:04:52 +01:00
dsehnal
f96211ff91 comment 2021-11-29 18:04:23 +01:00
dsehnal
77f9c02785 udpate presets 2021-11-29 18:01:41 +01:00
dsehnal
7910b65fdc Merge branch 'master' of https://github.com/molstar/molstar 2021-11-29 14:49:53 +01:00
dsehnal
eb4fc4588d remove console.log 2021-11-29 14:49:51 +01:00
David Sehnal
5430674071 Merge pull request #294 from MadCatX/fixtypo
Fix typo in README
2021-11-29 14:10:20 +01:00
dsehnal
17e67e3b79 Refactor Material representation 2021-11-29 14:09:56 +01:00
dsehnal
e87a0d72e4 Fix docking viewer material usage 2021-11-29 12:48:26 +01:00
dsehnal
67d3c65907 ParamDefinition.Group.presets support 2021-11-29 12:35:57 +01:00
dsehnal
564a5486c9 StructureComponentManager.Options state saving support 2021-11-29 11:57:37 +01:00
Michal Malý
9ce11c4c32 Fix typo in README 2021-11-29 11:41:57 +01:00
dsehnal
5e97b551a5 changelog 2021-11-29 11:10:30 +01:00
David Sehnal
77536e75af Merge pull request #292 from MadCatX/provide-rebuild-cmd
Add packaging command to force a full rebuild
2021-11-29 11:05:44 +01:00
Alexander Rose
6f12f714d2 3.0.0-dev.0 2021-11-28 14:02:46 -08:00
Alexander Rose
1f67077400 changelog 2021-11-28 13:56:07 -08:00
Alexander Rose
d1c4cf69cb Merge pull request #291 from molstar/lighting
Lighting
2021-11-28 13:53:42 -08:00
Alexander Rose
803c5eaa15 changelog 2021-11-28 13:39:53 -08:00
Alexander Rose
970fd5d9c3 Merge branch 'master' of https://github.com/molstar/molstar into lighting 2021-11-28 13:32:56 -08:00
Alexander Rose
7ccd4a1e0d 2.4.1 2021-11-28 13:31:12 -08:00
Michal Malý
3a6ab55266 Add a cleanup packaging script 2021-11-28 22:29:50 +01:00
Alexander Rose
eb41882c56 Merge branch 'master' of https://github.com/molstar/molstar 2021-11-28 13:25:42 -08:00
Alexander Rose
734851a810 changelog 2021-11-28 12:46:12 -08:00
Alexander Rose
6318717a15 tweak material param 2021-11-28 12:42:33 -08:00
dsehnal
d8498feaef updade npmignore 2021-11-28 14:36:54 +01:00
Michal Malý
aaec452bc2 Add packaging command to force a full rebuild 2021-11-28 13:28:06 +01:00
Alexander Rose
bce959195a fix renderer spec baseline 2021-11-27 17:31:42 -08:00
Alexander Rose
4287e09a9a fix material param number formating 2021-11-27 17:25:51 -08:00
Alexander Rose
fdc006f833 Merge branch 'master' of https://github.com/molstar/molstar into lighting 2021-11-27 17:08:56 -08:00
Alexander Rose
c704b7505c add Substance theming (per-group material) 2021-11-27 17:06:55 -08:00
Alexander Rose
ceaf238322 material improvements
- material helpers
- material in structure component manager
2021-11-27 17:02:48 -08:00
Alexander Rose
b7224ce5c7 lighting tweaks 2021-11-26 18:04:33 -08:00
Alexander Rose
95654175fe improve 'rounded' tube geometry
- correct normals
- circle offset
2021-11-26 15:34:16 -08:00
Alexander Rose
de96244706 allow atoms in aromatic rings to do hydrogen bonds 2021-11-26 14:37:50 -08:00
Alexander Rose
3104ee5742 Merge branch 'master' of https://github.com/molstar/molstar into lighting 2021-11-25 15:00:19 -08:00
Alexander Rose
1e4d1e45f9 Merge branch 'master' of https://github.com/molstar/molstar into lighting 2021-11-13 13:36:03 -08:00
Alexander Rose
b5ccdfdd53 multiple lights, per object materials
- multiple directional lights
- per-object materials (roughness & metalness)
- fixed reflectivity to 0.5
- update PhysicalMaterial from three.js to r134
2021-11-06 21:14:14 -07:00
Alexander Rose
d0c0d8e703 require standardDerivatives glsl extension 2021-11-06 20:57:22 -07:00
Alexander Rose
ebf64404be add loop unrolling glsl support 2021-11-06 15:02:14 -07:00
Alexander Rose
e5dcc8e54f wip, lighting improvements
- set light direction
- set light color
- set ambient color
2021-10-31 14:59:19 -07:00
63 changed files with 1239 additions and 338 deletions

View File

@@ -1 +1,5 @@
tsconfig.commonjs.tsbuildinfo
tests
perf-tests
_spec
*.tsbuildinfo
*.js.map

View File

@@ -6,6 +6,27 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v3.0.0-dev.3] - 2021-12-4
- Fix OBJ and USDZ export
## [v3.0.0-dev.2] - 2021-12-1
- Do not include tests and source maps in NPM package
## [v3.0.0-dev.0] - 2021-11-28
- Add multiple lights support (with color, intensity, and direction parameters)
- [Breaking] Add per-object material rendering properties
- ``SimpleSettingsParams.lighting.renderStyle`` and ``RendererParams.style`` were removed
- Add substance theme with per-group material rendering properties
- ``StructureComponentManager.Options`` state saving support
- ``ParamDefinition.Group.presets`` support
## [v2.4.1] - 2021-11-28
- Fix: allow atoms in aromatic rings to do hydrogen bonds
## [v2.4.0] - 2021-11-25
- Fix secondary-structure property handling

View File

@@ -68,6 +68,17 @@ If working on just the viewer, ``npm run watch-viewer`` will provide shorter com
Debug/production mode in browsers can be turned on/off during runtime by calling ``setMolStarDebugMode(true/false, true/false)`` from the dev console.
### Cleaning and forcing a full rebuild
npm run clean
Wipes the `build` and `lib` directories and `.tsbuildinfo` files.
npm run rebuild
Runs the cleanup script prior to building the project, forcing a full rebuild of the project.
Use these commands to resolve occassional build failures which may arise after some dependency updates. Once done, `npm run build` should work again. Note that full rebuilds take more time to complete.
### Build for production:
NODE_ENV=production npm run build

144
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "molstar",
"version": "2.4.0",
"version": "3.0.0-dev.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "molstar",
"version": "2.4.0",
"version": "3.0.0-dev.3",
"license": "MIT",
"dependencies": {
"@types/argparse": "^2.0.10",
@@ -56,8 +56,8 @@
"@types/cors": "^2.8.12",
"@types/gl": "^4.1.0",
"@types/jest": "^27.0.3",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.0",
"benchmark": "^2.1.4",
"concurrently": "^6.4.0",
"cpx2": "^4.0.0",
@@ -2885,13 +2885,13 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.4.0.tgz",
"integrity": "sha512-9/yPSBlwzsetCsGEn9j24D8vGQgJkOTr4oMLas/w886ZtzKIs1iyoqFrwsX2fqYEeUwsdBpC21gcjRGo57u0eg==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz",
"integrity": "sha512-4bV6fulqbuaO9UMXU0Ia0o6z6if+kmMRW8rMRyfqXj/eGrZZRGedS4n0adeGNnjr8LKAM495hrQ7Tea52UWmQA==",
"dev": true,
"dependencies": {
"@typescript-eslint/experimental-utils": "5.4.0",
"@typescript-eslint/scope-manager": "5.4.0",
"@typescript-eslint/experimental-utils": "5.5.0",
"@typescript-eslint/scope-manager": "5.5.0",
"debug": "^4.3.2",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.1.8",
@@ -2917,15 +2917,15 @@
}
},
"node_modules/@typescript-eslint/experimental-utils": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.4.0.tgz",
"integrity": "sha512-Nz2JDIQUdmIGd6p33A+naQmwfkU5KVTLb/5lTk+tLVTDacZKoGQisj8UCxk7onJcrgjIvr8xWqkYI+DbI3TfXg==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.5.0.tgz",
"integrity": "sha512-kjWeeVU+4lQ1SLYErRKV5yDXbWDPkpbzTUUlfAUifPYvpX0qZlrcCZ96/6oWxt3QxtK5WVhXz+KsnwW9cIW+3A==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.4.0",
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/typescript-estree": "5.4.0",
"@typescript-eslint/scope-manager": "5.5.0",
"@typescript-eslint/types": "5.5.0",
"@typescript-eslint/typescript-estree": "5.5.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
},
@@ -2941,14 +2941,14 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.4.0.tgz",
"integrity": "sha512-JoB41EmxiYpaEsRwpZEYAJ9XQURPFer8hpkIW9GiaspVLX8oqbqNM8P4EP8HOZg96yaALiLEVWllA2E8vwsIKw==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.5.0.tgz",
"integrity": "sha512-JsXBU+kgQOAgzUn2jPrLA+Rd0Y1dswOlX3hp8MuRO1hQDs6xgHtbCXEiAu7bz5hyVURxbXcA2draasMbNqrhmg==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.4.0",
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/typescript-estree": "5.4.0",
"@typescript-eslint/scope-manager": "5.5.0",
"@typescript-eslint/types": "5.5.0",
"@typescript-eslint/typescript-estree": "5.5.0",
"debug": "^4.3.2"
},
"engines": {
@@ -2968,13 +2968,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.4.0.tgz",
"integrity": "sha512-pRxFjYwoi8R+n+sibjgF9iUiAELU9ihPBtHzocyW8v8D8G8KeQvXTsW7+CBYIyTYsmhtNk50QPGLE3vrvhM5KA==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.5.0.tgz",
"integrity": "sha512-0/r656RmRLo7CbN4Mdd+xZyPJ/fPCKhYdU6mnZx+8msAD8nJSP8EyCFkzbd6vNVZzZvWlMYrSNekqGrCBqFQhg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/visitor-keys": "5.4.0"
"@typescript-eslint/types": "5.5.0",
"@typescript-eslint/visitor-keys": "5.5.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2985,9 +2985,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.4.0.tgz",
"integrity": "sha512-GjXNpmn+n1LvnttarX+sPD6+S7giO+9LxDIGlRl4wK3a7qMWALOHYuVSZpPTfEIklYjaWuMtfKdeByx0AcaThA==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.5.0.tgz",
"integrity": "sha512-OaYTqkW3GnuHxqsxxJ6KypIKd5Uw7bFiQJZRyNi1jbMJnK3Hc/DR4KwB6KJj6PBRkJJoaNwzMNv9vtTk87JhOg==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2998,13 +2998,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.4.0.tgz",
"integrity": "sha512-nhlNoBdhKuwiLMx6GrybPT3SFILm5Gij2YBdPEPFlYNFAXUJWX6QRgvi/lwVoadaQEFsizohs6aFRMqsXI2ewA==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.5.0.tgz",
"integrity": "sha512-pVn8btYUiYrjonhMAO0yG8lm7RApzy2L4RC7Td/mC/qFkyf6vRbGyZozoA94+w6D2Y2GRqpMoCWcwx/EUOzyoQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/visitor-keys": "5.4.0",
"@typescript-eslint/types": "5.5.0",
"@typescript-eslint/visitor-keys": "5.5.0",
"debug": "^4.3.2",
"globby": "^11.0.4",
"is-glob": "^4.0.3",
@@ -3025,12 +3025,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.4.0.tgz",
"integrity": "sha512-PVbax7MeE7tdLfW5SA0fs8NGVVr+buMPrcj+CWYWPXsZCH8qZ1THufDzbXm1xrZ2b2PA1iENJ0sRq5fuUtvsJg==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.5.0.tgz",
"integrity": "sha512-4GzJ1kRtsWzHhdM40tv0ZKHNSbkDhF0Woi/TDwVJX6UICwJItvP7ZTXbjTkCdrors7ww0sYe0t+cIKDAJwZ7Kw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/types": "5.5.0",
"eslint-visitor-keys": "^3.0.0"
},
"engines": {
@@ -16064,13 +16064,13 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.4.0.tgz",
"integrity": "sha512-9/yPSBlwzsetCsGEn9j24D8vGQgJkOTr4oMLas/w886ZtzKIs1iyoqFrwsX2fqYEeUwsdBpC21gcjRGo57u0eg==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz",
"integrity": "sha512-4bV6fulqbuaO9UMXU0Ia0o6z6if+kmMRW8rMRyfqXj/eGrZZRGedS4n0adeGNnjr8LKAM495hrQ7Tea52UWmQA==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "5.4.0",
"@typescript-eslint/scope-manager": "5.4.0",
"@typescript-eslint/experimental-utils": "5.5.0",
"@typescript-eslint/scope-manager": "5.5.0",
"debug": "^4.3.2",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.1.8",
@@ -16080,55 +16080,55 @@
}
},
"@typescript-eslint/experimental-utils": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.4.0.tgz",
"integrity": "sha512-Nz2JDIQUdmIGd6p33A+naQmwfkU5KVTLb/5lTk+tLVTDacZKoGQisj8UCxk7onJcrgjIvr8xWqkYI+DbI3TfXg==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.5.0.tgz",
"integrity": "sha512-kjWeeVU+4lQ1SLYErRKV5yDXbWDPkpbzTUUlfAUifPYvpX0qZlrcCZ96/6oWxt3QxtK5WVhXz+KsnwW9cIW+3A==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.4.0",
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/typescript-estree": "5.4.0",
"@typescript-eslint/scope-manager": "5.5.0",
"@typescript-eslint/types": "5.5.0",
"@typescript-eslint/typescript-estree": "5.5.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
}
},
"@typescript-eslint/parser": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.4.0.tgz",
"integrity": "sha512-JoB41EmxiYpaEsRwpZEYAJ9XQURPFer8hpkIW9GiaspVLX8oqbqNM8P4EP8HOZg96yaALiLEVWllA2E8vwsIKw==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.5.0.tgz",
"integrity": "sha512-JsXBU+kgQOAgzUn2jPrLA+Rd0Y1dswOlX3hp8MuRO1hQDs6xgHtbCXEiAu7bz5hyVURxbXcA2draasMbNqrhmg==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.4.0",
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/typescript-estree": "5.4.0",
"@typescript-eslint/scope-manager": "5.5.0",
"@typescript-eslint/types": "5.5.0",
"@typescript-eslint/typescript-estree": "5.5.0",
"debug": "^4.3.2"
}
},
"@typescript-eslint/scope-manager": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.4.0.tgz",
"integrity": "sha512-pRxFjYwoi8R+n+sibjgF9iUiAELU9ihPBtHzocyW8v8D8G8KeQvXTsW7+CBYIyTYsmhtNk50QPGLE3vrvhM5KA==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.5.0.tgz",
"integrity": "sha512-0/r656RmRLo7CbN4Mdd+xZyPJ/fPCKhYdU6mnZx+8msAD8nJSP8EyCFkzbd6vNVZzZvWlMYrSNekqGrCBqFQhg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/visitor-keys": "5.4.0"
"@typescript-eslint/types": "5.5.0",
"@typescript-eslint/visitor-keys": "5.5.0"
}
},
"@typescript-eslint/types": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.4.0.tgz",
"integrity": "sha512-GjXNpmn+n1LvnttarX+sPD6+S7giO+9LxDIGlRl4wK3a7qMWALOHYuVSZpPTfEIklYjaWuMtfKdeByx0AcaThA==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.5.0.tgz",
"integrity": "sha512-OaYTqkW3GnuHxqsxxJ6KypIKd5Uw7bFiQJZRyNi1jbMJnK3Hc/DR4KwB6KJj6PBRkJJoaNwzMNv9vtTk87JhOg==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.4.0.tgz",
"integrity": "sha512-nhlNoBdhKuwiLMx6GrybPT3SFILm5Gij2YBdPEPFlYNFAXUJWX6QRgvi/lwVoadaQEFsizohs6aFRMqsXI2ewA==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.5.0.tgz",
"integrity": "sha512-pVn8btYUiYrjonhMAO0yG8lm7RApzy2L4RC7Td/mC/qFkyf6vRbGyZozoA94+w6D2Y2GRqpMoCWcwx/EUOzyoQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/visitor-keys": "5.4.0",
"@typescript-eslint/types": "5.5.0",
"@typescript-eslint/visitor-keys": "5.5.0",
"debug": "^4.3.2",
"globby": "^11.0.4",
"is-glob": "^4.0.3",
@@ -16137,12 +16137,12 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.4.0.tgz",
"integrity": "sha512-PVbax7MeE7tdLfW5SA0fs8NGVVr+buMPrcj+CWYWPXsZCH8qZ1THufDzbXm1xrZ2b2PA1iENJ0sRq5fuUtvsJg==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.5.0.tgz",
"integrity": "sha512-4GzJ1kRtsWzHhdM40tv0ZKHNSbkDhF0Woi/TDwVJX6UICwJItvP7ZTXbjTkCdrors7ww0sYe0t+cIKDAJwZ7Kw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/types": "5.5.0",
"eslint-visitor-keys": "^3.0.0"
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "2.4.0",
"version": "3.0.0-dev.3",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -16,6 +16,8 @@
"test": "npm run lint && jest",
"jest": "jest",
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
"clean": "node ./scripts/clean.js",
"rebuild": "npm run clean && npm run build",
"build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
"build-tsc": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\"",
"build-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/",
@@ -36,7 +38,7 @@
"volume-server-test": "node lib/commonjs/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
"plugin-state": "node lib/commonjs/servers/plugin-state/index.js --working-folder ./build/state --port 1339",
"preversion": "npm run test",
"version": "npm run build",
"version": "npm run rebuild && cpx .npmignore lib/",
"postversion": "git push && git push --tags"
},
"files": [
@@ -98,8 +100,8 @@
"@types/cors": "^2.8.12",
"@types/gl": "^4.1.0",
"@types/jest": "^27.0.3",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.0",
"benchmark": "^2.1.4",
"concurrently": "^6.4.0",
"cpx2": "^4.0.0",

19
scripts/clean.js Normal file
View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <malym@ibt.cas.cz>
*/
const fs = require('fs');
const path = require('path');
const toClean = [
path.resolve(__dirname, '../build'),
path.resolve(__dirname, '../lib'),
path.resolve(__dirname, '../tsconfig.tsbuildinfo'),
];
toClean.forEach(ph => {
if (fs.existsSync(ph))
fs.rm(ph, { recursive: true }, err => { if (err) console.warn(`Failed to delete ${ph}: ${err}`); });
});

View File

@@ -21,12 +21,12 @@ import { PluginContext } from '../../mol-plugin/context';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { StateObjectRef } from '../../mol-state';
import { Color } from '../../mol-util/color';
import { Material } from '../../mol-util/material';
function shinyStyle(plugin: PluginContext) {
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
style: { name: 'plastic', params: {} },
},
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
@@ -40,7 +40,6 @@ function occlusionStyle(plugin: PluginContext) {
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
style: { name: 'flat', params: {} }
},
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
@@ -77,7 +76,7 @@ const PresetParams = {
...StructureRepresentationPresetProvider.CommonParams,
};
const CustomMaterial = Material({ roughness: 0.2, metalness: 0 });
export const StructurePreset = StructureRepresentationPresetProvider({
id: 'preset-structure',
@@ -94,8 +93,8 @@ export const StructurePreset = StructureRepresentationPresetProvider({
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, material: CustomMaterial }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
@@ -121,8 +120,8 @@ export const IllustrativePreset = StructureRepresentationPresetProvider({
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
@@ -149,8 +148,8 @@ const SurfacePreset = StructureRepresentationPresetProvider({
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, material: CustomMaterial, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
@@ -177,8 +176,8 @@ const PocketPreset = StructureRepresentationPresetProvider({
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, material: CustomMaterial, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
};
await update.commit({ revertOnError: true });
@@ -206,10 +205,10 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, material: CustomMaterial, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
};
await update.commit({ revertOnError: true });

View File

@@ -4,7 +4,6 @@
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { getStyle } from '../../mol-gl/renderer';
import { Box3D } from '../../mol-math/geometry';
import { PluginComponent } from '../../mol-plugin-state/component';
import { PluginContext } from '../../mol-plugin/context';
@@ -46,13 +45,12 @@ export class GeometryControls extends PluginComponent {
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
const filename = this.getFilename();
const style = getStyle(this.plugin.canvas3d?.props.renderer.style!);
const boundingSphere = this.plugin.canvas3d?.boundingSphereVisible!;
const boundingBox = Box3D.fromSphere3D(Box3D(), boundingSphere);
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter | UsdzExporter;
switch (this.behaviors.params.value.format) {
case 'glb':
renderObjectExporter = new GlbExporter(style, boundingBox);
renderObjectExporter = new GlbExporter(boundingBox);
break;
case 'obj':
renderObjectExporter = new ObjExporter(filename, boundingBox);
@@ -61,7 +59,7 @@ export class GeometryControls extends PluginComponent {
renderObjectExporter = new StlExporter(boundingBox);
break;
case 'usdz':
renderObjectExporter = new UsdzExporter(style, boundingBox, boundingSphere.radius);
renderObjectExporter = new UsdzExporter(boundingBox, boundingSphere.radius);
break;
default: throw new Error('Unsupported format.');
}

View File

@@ -5,7 +5,6 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Style } from '../../mol-gl/renderer';
import { asciiWrite } from '../../mol-io/common/ascii';
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
import { Box3D } from '../../mol-math/geometry';
@@ -44,6 +43,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
readonly fileExtension = 'glb';
private nodes: Record<string, any>[] = [];
private meshes: Record<string, any>[] = [];
private materials: Record<string, any>[] = [];
private materialMap = new Map<string, number>();
private accessors: Record<string, any>[] = [];
private bufferViews: Record<string, any>[] = [];
private binaryBuffer: ArrayBuffer[] = [];
@@ -157,6 +158,21 @@ export class GlbExporter extends MeshExporter<GlbData> {
return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
}
private addMaterial(metalness: number, roughness: number) {
const hash = `${metalness}|${roughness}`;
if (!this.materialMap.has(hash)) {
this.materialMap.set(hash, this.materials.length);
this.materials.push({
pbrMetallicRoughness: {
baseColorFactor: [1, 1, 1, 1],
metallicFactor: metalness,
roughnessFactor: roughness
}
});
}
return this.materialMap.get(hash)!;
}
protected async addMeshWithColors(input: AddMeshInput) {
const { mesh, values, isGeoTexture, webgl, ctx } = input;
@@ -168,6 +184,10 @@ export class GlbExporter extends MeshExporter<GlbData> {
const dTransparency = values.dTransparency.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
const metalness = values.uMetalness.ref.value;
const roughness = values.uRoughness.ref.value;
const material = this.addMaterial(metalness, roughness);
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
@@ -228,7 +248,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
COLOR_0: colorAccessorIndex!
},
indices: indexAccessorIndex,
material: 0
material
}]
});
}
@@ -262,13 +282,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
}],
bufferViews: this.bufferViews,
accessors: this.accessors,
materials: [{
pbrMetallicRoughness: {
baseColorFactor: [1, 1, 1, 1],
metallicFactor: this.style.metalness,
roughnessFactor: this.style.roughness
}
}]
materials: this.materials
};
const createChunk = (chunkType: number, data: ArrayBuffer[], byteLength: number, padChar: number): [ArrayBuffer[], number] => {
@@ -317,7 +331,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
return new Blob([(await this.getData()).glb], { type: 'model/gltf-binary' });
}
constructor(private style: Style, boundingBox: Box3D) {
constructor(boundingBox: Box3D) {
super();
const tmpV = Vec3();
Vec3.add(tmpV, boundingBox.min, boundingBox.max);

View File

@@ -145,7 +145,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
const color = ObjExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
Color.toArray(color, quantizedColors, i);
}
ObjExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
ObjExporter.quantizeColors(quantizedColors, vertexCount);
// face
for (let i = 0; i < drawCount; i += 3) {

View File

@@ -5,7 +5,6 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Style } from '../../mol-gl/renderer';
import { asciiWrite } from '../../mol-io/common/ascii';
import { Box3D } from '../../mol-math/geometry';
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
@@ -32,17 +31,16 @@ export class UsdzExporter extends MeshExporter<UsdzData> {
readonly fileExtension = 'usdz';
private meshes: string[] = [];
private materials: string[] = [];
private materialSet = new Set<number>();
private materialMap = new Map<string, number>();
private centerTransform: Mat4;
private static getMaterialKey(color: Color, alpha: number) {
return color * 256 + Math.round(alpha * 255);
}
private addMaterial(color: Color, alpha: number, metalness: number, roughness: number): number {
const hash = `${color}|${alpha}|${metalness}|${roughness}`;
if (this.materialMap.has(hash)) return this.materialMap.get(hash)!;
const materialKey = this.materialMap.size;
this.materialMap.set(hash, materialKey);
private addMaterial(color: Color, alpha: number) {
const materialKey = UsdzExporter.getMaterialKey(color, alpha);
if (this.materialSet.has(materialKey)) return;
this.materialSet.add(materialKey);
const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
this.materials.push(`
def Material "material${materialKey}"
@@ -53,12 +51,13 @@ def Material "material${materialKey}"
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (${r},${g},${b})
float inputs:opacity = ${alpha}
float inputs:metallic = ${this.style.metalness}
float inputs:roughness = ${this.style.roughness}
float inputs:metallic = ${metalness}
float inputs:roughness = ${roughness}
token outputs:surface
}
}
`);
return materialKey;
}
protected async addMeshWithColors(input: AddMeshInput) {
@@ -75,6 +74,8 @@ def Material "material${materialKey}"
const uAlpha = values.uAlpha.ref.value;
const aTransform = values.aTransform.ref.value;
const instanceCount = values.uInstanceCount.ref.value;
const metalness = values.uMetalness.ref.value;
const roughness = values.uRoughness.ref.value;
let interpolatedColors: Uint8Array | undefined;
if (colorType === 'volume' || colorType === 'volumeInstance') {
@@ -148,7 +149,7 @@ def Material "material${materialKey}"
const color = UsdzExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
Color.toArray(color, quantizedColors, i);
}
UsdzExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
UsdzExporter.quantizeColors(quantizedColors, vertexCount);
// material
const faceIndicesByMaterial = new Map<number, number[]>();
@@ -158,9 +159,7 @@ def Material "material${materialKey}"
const transparency = UsdzExporter.getTransparency(i, geoData, interpolatedTransparency);
const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
this.addMaterial(color, alpha);
const materialKey = UsdzExporter.getMaterialKey(color, alpha);
const materialKey = this.addMaterial(color, alpha, metalness, roughness);
let faceIndices = faceIndicesByMaterial.get(materialKey);
if (faceIndices === undefined) {
faceIndices = [];
@@ -232,7 +231,7 @@ def Mesh "mesh${this.meshes.length}"
return new Blob([usdz], { type: 'model/vnd.usdz+zip' });
}
constructor(private style: Style, boundingBox: Box3D, radius: number) {
constructor(boundingBox: Box3D, radius: number) {
super();
const t = Mat4();
// scale the model so that it fits within 1 meter

View File

@@ -16,6 +16,7 @@ import { NullLocation } from '../../mol-model/location';
import { UniformColorTheme } from '../../mol-theme/color/uniform';
import { UniformSizeTheme } from '../../mol-theme/size/uniform';
import { smoothstep } from '../../mol-math/interpolate';
import { Material } from '../../mol-util/material';
export const VisualQualityInfo = {
'custom': {},
@@ -69,18 +70,20 @@ export function getColorSmoothingProps(smoothColors: PD.Values<ColorSmoothingPar
//
export namespace BaseGeometry {
export const Params = {
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),
quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
};
export type Params = typeof Params
export const MaterialCategory: PD.Info = { category: 'Material' };
export const ShadingCategory: PD.Info = { category: 'Shading' };
export const CustomQualityParamInfo: PD.Info = {
category: 'Custom Quality',
hideIf: (params: PD.Values<Params>) => typeof params.quality !== 'undefined' && params.quality !== 'custom'
};
export const Params = {
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),
quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
material: Material.getParam(),
};
export type Params = typeof Params
export type Counts = { drawCount: number, vertexCount: number, groupCount: number, instanceCount: number }
export function createSimple(colorValue = ColorNames.grey, sizeValue = 1, transform?: TransformData) {
@@ -100,11 +103,16 @@ export namespace BaseGeometry {
uVertexCount: ValueCell.create(counts.vertexCount),
uGroupCount: ValueCell.create(counts.groupCount),
drawCount: ValueCell.create(counts.drawCount),
uMetalness: ValueCell.create(props.material.metalness),
uRoughness: ValueCell.create(props.material.roughness),
dLightCount: ValueCell.create(1),
};
}
export function updateValues(values: BaseValues, props: PD.Values<Params>) {
ValueCell.updateIfChanged(values.alpha, props.alpha); // `uAlpha` is set in renderable.render
ValueCell.updateIfChanged(values.uMetalness, props.material.metalness);
ValueCell.updateIfChanged(values.uRoughness, props.material.roughness);
}
export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {

View File

@@ -25,6 +25,7 @@ import { hashFnv32a } from '../../../mol-data/util';
import { createEmptyClipping } from '../clipping-data';
import { CylindersValues } from '../../../mol-gl/renderable/cylinders';
import { RenderableState } from '../../../mol-gl/renderable';
import { createEmptySubstance } from '../substance-data';
export interface Cylinders {
readonly kind: 'cylinders',
@@ -200,6 +201,7 @@ export namespace Cylinders {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: cylinders.cylinderCount * 4 * 3, vertexCount: cylinders.cylinderCount * 6, groupCount, instanceCount };
@@ -224,6 +226,7 @@ export namespace Cylinders {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,

View File

@@ -28,6 +28,7 @@ import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './
import { createEmptyClipping } from '../clipping-data';
import { Grid, Volume } from '../../../mol-model/volume';
import { ColorNames } from '../../../mol-util/color/names';
import { createEmptySubstance } from '../substance-data';
const VolumeBox = Box();
@@ -246,6 +247,7 @@ export namespace DirectVolume {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const [x, y, z] = gridDimension.ref.value;
@@ -270,6 +272,7 @@ export namespace DirectVolume {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,
...BaseGeometry.createValues(props, counts),
@@ -319,8 +322,7 @@ export namespace DirectVolume {
}
function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
ValueCell.updateIfChanged(values.alpha, props.alpha);
ValueCell.updateIfChanged(values.uAlpha, props.alpha);
BaseGeometry.updateValues(values, props);
ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided);
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);

View File

@@ -26,6 +26,7 @@ import { fillSerial } from '../../../mol-util/array';
import { createEmptyClipping } from '../clipping-data';
import { NullLocation } from '../../../mol-model/location';
import { QuadPositions } from '../../../mol-gl/compute/util';
import { createEmptySubstance } from '../substance-data';
const QuadIndices = new Uint32Array([
0, 1, 2,
@@ -145,6 +146,7 @@ namespace Image {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: QuadIndices.length, vertexCount: QuadPositions.length / 3, groupCount, instanceCount };
@@ -157,6 +159,7 @@ namespace Image {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,
...BaseGeometry.createValues(props, counts),

View File

@@ -26,6 +26,7 @@ import { createEmptyOverpaint } from '../overpaint-data';
import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { createEmptyClipping } from '../clipping-data';
import { createEmptySubstance } from '../substance-data';
/** Wide line */
export interface Lines {
@@ -210,6 +211,7 @@ export namespace Lines {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: lines.lineCount * 2 * 3, vertexCount: lines.lineCount * 4, groupCount, instanceCount };
@@ -231,6 +233,7 @@ export namespace Lines {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,

View File

@@ -6,7 +6,7 @@
*/
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { ChunkedArray } from '../../../../mol-data/util';
import { cantorPairing, ChunkedArray } from '../../../../mol-data/util';
import { MeshBuilder } from '../mesh-builder';
const normalVector = Vec3();
@@ -37,18 +37,20 @@ const v3unitX = Vec3.unitX;
const caAdd3 = ChunkedArray.add3;
const CosSinCache = new Map<number, { cos: number[], sin: number[] }>();
function getCosSin(radialSegments: number) {
if (!CosSinCache.has(radialSegments)) {
function getCosSin(radialSegments: number, shift: boolean) {
const offset = shift ? 1 : 0;
const hash = cantorPairing(radialSegments, offset);
if (!CosSinCache.has(hash)) {
const cos: number[] = [];
const sin: number[] = [];
for (let j = 0; j < radialSegments; ++j) {
const t = 2 * Math.PI * j / radialSegments;
const t = (j * 2 + offset) / radialSegments * Math.PI;
cos[j] = Math.cos(t);
sin[j] = Math.sin(t);
}
CosSinCache.set(radialSegments, { cos, sin });
CosSinCache.set(hash, { cos, sin });
}
return CosSinCache.get(radialSegments)!;
return CosSinCache.get(hash)!;
}
export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, radialSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, startCap: boolean, endCap: boolean, crossSection: 'elliptical' | 'rounded') {
@@ -56,9 +58,9 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
let vertexCount = vertices.elementCount;
const { cos, sin } = getCosSin(radialSegments);
const { cos, sin } = getCosSin(radialSegments, crossSection === 'rounded');
const q1 = radialSegments / 4;
const q1 = Math.round(radialSegments / 4);
const q3 = q1 * 3;
for (let i = 0; i <= linearSegments; ++i) {
@@ -78,7 +80,13 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
? (j < q1 || j >= q3) ? height - width : -height + width
: (j >= q1 && j < q3) ? -height + width : height - width;
v3scaleAndAdd(surfacePoint, surfacePoint, u, h);
add2AndScale2(normalVector, u, v, cos[j], sin[j]);
if (j === q1 || j === q1 - 1) {
add2AndScale2(normalVector, u, v, 0, 1);
} else if (j === q3 || j === q3 - 1) {
add2AndScale2(normalVector, u, v, 0, -1);
} else {
add2AndScale2(normalVector, u, v, cos[j], sin[j]);
}
} else {
add3AndScale2(surfacePoint, u, v, controlPoint, height * cos[j], width * sin[j]);
add2AndScale2(normalVector, u, v, width * cos[j], height * sin[j]);

View File

@@ -356,3 +356,37 @@ export function applyMeshTransparencySmoothing(values: MeshValues, resolution: n
ValueCell.update(values.uTransparencyTexDim, smoothingData.texDim);
}
}
function isSupportedSubstanceType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}
export function applyMeshSubstanceSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
if (!isSupportedSubstanceType(values.dSubstanceType.ref.value)) return;
const smoothingData = calcMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionBuffer: values.aPosition.ref.value,
groupBuffer: values.aGroup.ref.value,
colorData: values.tSubstance.ref.value,
colorType: values.dSubstanceType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
itemSize: 3
}, resolution, stride, webgl, colorTexture);
if (smoothingData.kind === 'volume') {
ValueCell.updateIfChanged(values.dSubstanceType, smoothingData.type);
ValueCell.update(values.tSubstanceGrid, smoothingData.texture);
ValueCell.update(values.uSubstanceTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uSubstanceGridDim, smoothingData.gridDim);
ValueCell.update(values.uSubstanceGridTransform, smoothingData.gridTransform);
} else if (smoothingData.kind === 'vertex') {
ValueCell.updateIfChanged(values.dSubstanceType, smoothingData.type);
ValueCell.update(values.tSubstance, smoothingData.texture);
ValueCell.update(values.uSubstanceTexDim, smoothingData.texDim);
}
}

View File

@@ -27,6 +27,7 @@ import { createEmptyClipping } from '../clipping-data';
import { RenderableState } from '../../../mol-gl/renderable';
import { arraySetAdd } from '../../../mol-util/array';
import { degToRad } from '../../../mol-math/misc';
import { createEmptySubstance } from '../substance-data';
export interface Mesh {
readonly kind: 'mesh',
@@ -665,6 +666,7 @@ export namespace Mesh {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: mesh.triangleCount * 3, vertexCount: mesh.vertexCount, groupCount, instanceCount };
@@ -684,6 +686,7 @@ export namespace Mesh {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,

View File

@@ -25,6 +25,7 @@ import { createEmptyOverpaint } from '../overpaint-data';
import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { createEmptyClipping } from '../clipping-data';
import { createEmptySubstance } from '../substance-data';
/** Point cloud */
export interface Points {
@@ -172,6 +173,7 @@ export namespace Points {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: points.pointCount, vertexCount: points.pointCount, groupCount, instanceCount };
@@ -190,6 +192,7 @@ export namespace Points {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,

View File

@@ -25,6 +25,7 @@ import { GroupMapping, createGroupMapping } from '../../util';
import { createEmptyClipping } from '../clipping-data';
import { Vec3, Vec4 } from '../../../mol-math/linear-algebra';
import { RenderableState } from '../../../mol-gl/renderable';
import { createEmptySubstance } from '../substance-data';
export interface Spheres {
readonly kind: 'spheres',
@@ -170,6 +171,7 @@ export namespace Spheres {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 4, groupCount, instanceCount };
@@ -191,6 +193,7 @@ export namespace Spheres {
...marker,
...overpaint,
...transparency,
...material,
...clipping,
...transform,

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ValueCell } from '../../mol-util/value-cell';
import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
import { Material } from '../../mol-util/material';
export type SubstanceData = {
tSubstance: ValueCell<TextureImage<Uint8Array>>
uSubstanceTexDim: ValueCell<Vec2>
dSubstance: ValueCell<boolean>,
tSubstanceGrid: ValueCell<Texture>,
uSubstanceGridDim: ValueCell<Vec3>,
uSubstanceGridTransform: ValueCell<Vec4>,
dSubstanceType: ValueCell<string>,
}
export function applySubstanceMaterial(array: Uint8Array, start: number, end: number, material: Material) {
for (let i = start; i < end; ++i) {
Material.toArray(material, array, i * 3);
array[i * 3 + 2] = 255;
}
return true;
}
export function clearSubstance(array: Uint8Array, start: number, end: number) {
array.fill(0, start * 3, end * 3);
return true;
}
export function createSubstance(count: number, substanceData?: SubstanceData): SubstanceData {
const substance = createTextureImage(Math.max(1, count), 4, Uint8Array, substanceData && substanceData.tSubstance.ref.value.array);
if (substanceData) {
ValueCell.update(substanceData.tSubstance, substance);
ValueCell.update(substanceData.uSubstanceTexDim, Vec2.create(substance.width, substance.height));
ValueCell.updateIfChanged(substanceData.dSubstance, count > 0);
return substanceData;
} else {
return {
tSubstance: ValueCell.create(substance),
uSubstanceTexDim: ValueCell.create(Vec2.create(substance.width, substance.height)),
dSubstance: ValueCell.create(count > 0),
tSubstanceGrid: ValueCell.create(createNullTexture()),
uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dSubstanceType: ValueCell.create('groupInstance'),
};
}
}
const emptySubstanceTexture = { array: new Uint8Array(4), width: 1, height: 1 };
export function createEmptySubstance(substanceData?: SubstanceData): SubstanceData {
if (substanceData) {
ValueCell.update(substanceData.tSubstance, emptySubstanceTexture);
ValueCell.update(substanceData.uSubstanceTexDim, Vec2.create(1, 1));
return substanceData;
} else {
return {
tSubstance: ValueCell.create(emptySubstanceTexture),
uSubstanceTexDim: ValueCell.create(Vec2.create(1, 1)),
dSubstance: ValueCell.create(false),
tSubstanceGrid: ValueCell.create(createNullTexture()),
uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
dSubstanceType: ValueCell.create('groupInstance'),
};
}
}

View File

@@ -29,6 +29,7 @@ import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { GroupMapping, createGroupMapping } from '../../util';
import { createEmptyClipping } from '../clipping-data';
import { createEmptySubstance } from '../substance-data';
type TextAttachment = (
'bottom-left' | 'bottom-center' | 'bottom-right' |
@@ -213,6 +214,7 @@ export namespace Text {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const substance = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: text.charCount * 2 * 3, vertexCount: text.charCount * 4, groupCount, instanceCount };
@@ -235,6 +237,7 @@ export namespace Text {
...marker,
...overpaint,
...transparency,
...substance,
...clipping,
...transform,

View File

@@ -500,3 +500,39 @@ export function applyTextureMeshTransparencySmoothing(values: TextureMeshValues,
ValueCell.update(values.uTransparencyGridDim, smoothingData.gridDim);
ValueCell.update(values.uTransparencyGridTransform, smoothingData.gridTransform);
}
function isSupportedSubstanceType(x: string): x is 'groupInstance' {
return x === 'groupInstance';
}
export function applyTextureMeshSubstanceSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
if (!isSupportedSubstanceType(values.dSubstanceType.ref.value)) return;
stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
if (!webgl.namedTextures[ColorSmoothingRgbName]) {
webgl.namedTextures[ColorSmoothingRgbName] = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'nearest');
}
const colorData = webgl.namedTextures[ColorSmoothingRgbName];
colorData.load(values.tSubstance.ref.value);
const smoothingData = calcTextureMeshColorSmoothing({
vertexCount: values.uVertexCount.ref.value,
instanceCount: values.uInstanceCount.ref.value,
groupCount: values.uGroupCount.ref.value,
transformBuffer: values.aTransform.ref.value,
instanceBuffer: values.aInstance.ref.value,
positionTexture: values.tPosition.ref.value,
groupTexture: values.tGroup.ref.value,
colorData,
colorType: values.dSubstanceType.ref.value,
boundingSphere: values.boundingSphere.ref.value,
invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
}, resolution, stride, webgl, colorTexture);
ValueCell.updateIfChanged(values.dSubstanceType, smoothingData.type);
ValueCell.update(values.tSubstanceGrid, smoothingData.texture);
ValueCell.update(values.uSubstanceTexDim, smoothingData.gridTexDim);
ValueCell.update(values.uSubstanceGridDim, smoothingData.gridDim);
ValueCell.update(values.uSubstanceGridTransform, smoothingData.gridTransform);
}

View File

@@ -23,6 +23,7 @@ import { createNullTexture, Texture } from '../../../mol-gl/webgl/texture';
import { Vec2, Vec4 } from '../../../mol-math/linear-algebra';
import { createEmptyClipping } from '../clipping-data';
import { NullLocation } from '../../../mol-model/location';
import { createEmptySubstance } from '../substance-data';
export interface TextureMesh {
readonly kind: 'texture-mesh',
@@ -135,6 +136,7 @@ export namespace TextureMesh {
const marker = createMarkers(instanceCount * groupCount);
const overpaint = createEmptyOverpaint();
const transparency = createEmptyTransparency();
const substance = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: textureMesh.vertexCount, vertexCount: textureMesh.vertexCount, groupCount, instanceCount };
@@ -156,6 +158,7 @@ export namespace TextureMesh {
...marker,
...overpaint,
...transparency,
...substance,
...clipping,
...transform,

View File

@@ -52,7 +52,7 @@ describe('renderer', () => {
scene.add(points);
scene.commit();
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
expect(ctx.stats.resourceCounts.texture).toBe(7);
expect(ctx.stats.resourceCounts.texture).toBe(8);
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 8 : 0);
expect(ctx.stats.resourceCounts.program).toBe(8);
expect(ctx.stats.resourceCounts.shader).toBe(16);

View File

@@ -10,7 +10,6 @@ import { createGraphicsRenderItem } from '../webgl/render-item';
import { GlobalUniformSchema, BaseSchema, AttributeSpec, DefineSpec, Values, InternalSchema, SizeSchema, InternalValues, GlobalTextureSchema } from './schema';
import { PointsShaderCode } from '../shader-code';
import { ValueCell } from '../../mol-util';
import { Points } from '../../mol-geo/geometry/points/points';
export const PointsSchema = {
...BaseSchema,
@@ -18,7 +17,7 @@ export const PointsSchema = {
aGroup: AttributeSpec('float32', 1, 0),
aPosition: AttributeSpec('float32', 3, 0),
dPointSizeAttenuation: DefineSpec('boolean'),
dPointStyle: DefineSpec('string', Points.StyleTypeNames),
dPointStyle: DefineSpec('string', ['square', 'circle', 'fuzzy']),
};
export type PointsSchema = typeof PointsSchema
export type PointsValues = Values<PointsSchema>

View File

@@ -141,15 +141,9 @@ export const GlobalUniformSchema = {
uClipObjectRotation: UniformSpec('v4[]'),
uClipObjectScale: UniformSpec('v3[]'),
// all the following could in principle be per object
// as a kind of 'material' parameter set
// would need to test performance implications
uLightIntensity: UniformSpec('f'),
uAmbientIntensity: UniformSpec('f'),
uMetalness: UniformSpec('f'),
uRoughness: UniformSpec('f'),
uReflectivity: UniformSpec('f'),
uLightDirection: UniformSpec('v3[]'),
uLightColor: UniformSpec('v3[]'),
uAmbientColor: UniformSpec('v3'),
uPickingAlphaThreshold: UniformSpec('f'),
@@ -247,6 +241,19 @@ export const TransparencySchema = {
export type TransparencySchema = typeof TransparencySchema
export type TransparencyValues = Values<TransparencySchema>
export const SubstanceSchema = {
uSubstanceTexDim: UniformSpec('v2'),
tSubstance: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
dSubstance: DefineSpec('boolean'),
uSubstanceGridDim: UniformSpec('v3'),
uSubstanceGridTransform: UniformSpec('v4'),
tSubstanceGrid: TextureSpec('texture', 'rgb', 'ubyte', 'linear'),
dSubstanceType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
} as const;
export type SubstanceSchema = typeof SubstanceSchema
export type SubstanceValues = Values<SubstanceSchema>
export const ClippingSchema = {
dClipObjectCount: DefineSpec('number'),
dClipVariant: DefineSpec('string', ['instance', 'pixel']),
@@ -263,8 +270,11 @@ export const BaseSchema = {
...MarkerSchema,
...OverpaintSchema,
...TransparencySchema,
...SubstanceSchema,
...ClippingSchema,
dLightCount: DefineSpec('number'),
aInstance: AttributeSpec('float32', 1, 1),
/**
* final per-instance transform calculated for instance `i` as
@@ -276,6 +286,9 @@ export const BaseSchema = {
* final alpha, calculated as `values.alpha * state.alpha`
*/
uAlpha: UniformSpec('f', 'material'),
uMetalness: UniformSpec('f', 'material'),
uRoughness: UniformSpec('f', 'material'),
uVertexCount: UniformSpec('i'),
uInstanceCount: UniformSpec('i'),
uGroupCount: UniformSpec('i'),

View File

@@ -70,7 +70,6 @@ interface Renderer {
export const RendererParams = {
backgroundColor: PD.Color(Color(0x000000), { description: 'Background color of the 3D canvas' }),
// the following are general 'material' parameters
pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
@@ -85,20 +84,19 @@ export const RendererParams = {
xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
style: PD.MappedStatic('matte', {
custom: PD.Group({
lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
}, { isExpanded: true }),
flat: PD.Group({}),
matte: PD.Group({}),
glossy: PD.Group({}),
metallic: PD.Group({}),
plastic: PD.Group({}),
}, { label: 'Lighting', description: 'Style in which the 3D scene is rendered/lighted' }),
light: PD.ObjectList({
inclination: PD.Numeric(180, { min: 0, max: 180, step: 1 }),
azimuth: PD.Numeric(0, { min: 0, max: 360, step: 1 }),
color: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
intensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
}, o => Color.toHexString(o.color), { defaultValue: [{
inclination: 180,
azimuth: 0,
color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
intensity: 0.6
}] }),
ambientColor: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
clip: PD.Group({
variant: PD.Select('instance', PD.arrayToOptions<Clipping.Variant>(['instance', 'pixel'])),
@@ -116,44 +114,27 @@ export const RendererParams = {
};
export type RendererProps = PD.Values<typeof RendererParams>
export type Style = {
lightIntensity: number
ambientIntensity: number
metalness: number
roughness: number
reflectivity: number
type Light = {
count: number
direction: number[]
color: number[]
}
export function getStyle(props: RendererProps['style']): Style {
switch (props.name) {
case 'custom':
return props.params as Style;
case 'flat':
return {
lightIntensity: 0, ambientIntensity: 1,
metalness: 0, roughness: 0.4, reflectivity: 0.5
};
case 'matte':
return {
lightIntensity: 0.7, ambientIntensity: 0.3,
metalness: 0, roughness: 1, reflectivity: 0.5
};
case 'glossy':
return {
lightIntensity: 0.7, ambientIntensity: 0.3,
metalness: 0, roughness: 0.4, reflectivity: 0.5
};
case 'metallic':
return {
lightIntensity: 0.7, ambientIntensity: 0.7,
metalness: 0.6, roughness: 0.6, reflectivity: 0.5
};
case 'plastic':
return {
lightIntensity: 0.7, ambientIntensity: 0.3,
metalness: 0, roughness: 0.2, reflectivity: 0.5
};
const tmpDir = Vec3();
const tmpColor = Vec3();
function getLight(props: RendererProps['light'], light?: Light): Light {
const { direction, color } = light || {
direction: (new Array(5 * 3)).fill(0),
color: (new Array(5 * 3)).fill(0),
};
for (let i = 0, il = props.length; i < il; ++i) {
const p = props[i];
Vec3.directionFromSpherical(tmpDir, degToRad(p.inclination), degToRad(p.azimuth), 1);
Vec3.toArray(tmpDir, direction, i * 3);
Vec3.scale(tmpColor, Color.toVec3Normalized(tmpColor, p.color), p.intensity);
Vec3.toArray(tmpColor, color, i * 3);
}
return { count: props.length, direction, color };
}
type Clip = {
@@ -195,7 +176,7 @@ namespace Renderer {
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
const { gl, state, stats, extensions: { fragDepth } } = ctx;
const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
const style = getStyle(p.style);
const light = getLight(p.light);
const clip = getClip(p.clip);
const viewport = Viewport();
@@ -220,6 +201,9 @@ namespace Renderer {
const cameraDir = Vec3();
const viewOffset = Vec2();
const ambientColor = Vec3();
Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
const globalUniforms: GlobalUniformValues = {
uModel: ValueCell.create(Mat4.identity()),
uView: ValueCell.create(view),
@@ -257,13 +241,9 @@ namespace Renderer {
uClipObjectRotation: ValueCell.create(clip.objects.rotation),
uClipObjectScale: ValueCell.create(clip.objects.scale),
// the following are general 'material' uniforms
uLightIntensity: ValueCell.create(style.lightIntensity),
uAmbientIntensity: ValueCell.create(style.ambientIntensity),
uMetalness: ValueCell.create(style.metalness),
uRoughness: ValueCell.create(style.roughness),
uReflectivity: ValueCell.create(style.reflectivity),
uLightDirection: ValueCell.create(light.direction),
uLightColor: ValueCell.create(light.color),
uAmbientColor: ValueCell.create(ambientColor),
uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
@@ -304,6 +284,10 @@ namespace Renderer {
definesNeedUpdate = true;
}
}
if (r.values.dLightCount.ref.value !== light.count) {
ValueCell.update(r.values.dLightCount, light.count);
definesNeedUpdate = true;
}
if (definesNeedUpdate) r.update();
const program = r.getProgram(variant);
@@ -686,14 +670,21 @@ namespace Renderer {
ValueCell.update(globalUniforms.uXrayEdgeFalloff, p.xrayEdgeFalloff);
}
if (props.style !== undefined) {
p.style = props.style;
Object.assign(style, getStyle(props.style));
ValueCell.updateIfChanged(globalUniforms.uLightIntensity, style.lightIntensity);
ValueCell.updateIfChanged(globalUniforms.uAmbientIntensity, style.ambientIntensity);
ValueCell.updateIfChanged(globalUniforms.uMetalness, style.metalness);
ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness);
ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.reflectivity);
if (props.light !== undefined && !deepEqual(props.light, p.light)) {
p.light = props.light;
Object.assign(light, getLight(props.light, light));
ValueCell.update(globalUniforms.uLightDirection, light.direction);
ValueCell.update(globalUniforms.uLightColor, light.color);
}
if (props.ambientColor !== undefined && props.ambientColor !== p.ambientColor) {
p.ambientColor = props.ambientColor;
Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
}
if (props.ambientIntensity !== undefined && props.ambientIntensity !== p.ambientIntensity) {
p.ambientIntensity = props.ambientIntensity;
Vec3.scale(ambientColor, Color.toArrayNormalized(p.ambientColor, ambientColor, 0), p.ambientIntensity);
ValueCell.update(globalUniforms.uAmbientColor, ambientColor);
}
if (props.clip !== undefined && !deepEqual(props.clip, p.clip)) {

View File

@@ -17,7 +17,6 @@ const shaderCodeId = idFactory();
type ShaderExtensionsValue = 'required' | 'optional'
export interface ShaderExtensions {
readonly standardDerivatives?: ShaderExtensionsValue
readonly fragDepth?: ShaderExtensionsValue
readonly drawBuffers?: ShaderExtensionsValue
readonly shaderTextureLod?: ShaderExtensionsValue
@@ -101,7 +100,8 @@ const ShaderChunks: { [k: string]: string } = {
wboit_write
};
const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gmi;
const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gm;
const reUnrollLoop = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*\+\+i\s*\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;
const reSingleLineComment = /[ \t]*\/\/.*\n/g;
const reMultiLineComment = /[ \t]*\/\*[\s\S]*?\*\//g;
const reMultipleLinebreaks = /\n{2,}/g;
@@ -119,6 +119,30 @@ function addIncludes(text: string) {
.replace(reMultipleLinebreaks, '\n');
}
function unrollLoops(str: string) {
return str.replace(reUnrollLoop, loopReplacer);
}
function loopReplacer(match: string, start: string, end: string, snippet: string) {
let out = '';
for (let i = parseInt(start); i < parseInt(end); ++i) {
out += snippet
.replace(/\[\s*i\s*\]/g, `[${i}]`)
.replace(/UNROLLED_LOOP_INDEX/g, `${i}`);
}
return out;
}
function replaceCounts(str: string, defines: ShaderDefines) {
if (defines.dLightCount) str = str.replace(/dLightCount/g, `${defines.dLightCount.ref.value}`);
if (defines.dClipObjectCount) str = str.replace(/dClipObjectCount/g, `${defines.dClipObjectCount.ref.value}`);
return str;
}
function preprocess(str: string, defines: ShaderDefines) {
return unrollLoops(replaceCounts(str, defines));
}
export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}, outTypes: FragOutTypes = {}): ShaderCode {
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions, outTypes };
}
@@ -139,7 +163,7 @@ export const CylindersShaderCode = ShaderCode('cylinders', cylinders_vert, cylin
import { text_vert } from './shader/text.vert';
import { text_frag } from './shader/text.frag';
export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: 'required', drawBuffers: 'optional' });
export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { drawBuffers: 'optional' });
import { lines_vert } from './shader/lines.vert';
import { lines_frag } from './shader/lines.frag';
@@ -147,7 +171,7 @@ export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag, { dra
import { mesh_vert } from './shader/mesh.vert';
import { mesh_frag } from './shader/mesh.frag';
export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: 'optional', drawBuffers: 'optional' });
export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { drawBuffers: 'optional' });
import { directVolume_vert } from './shader/direct-volume.vert';
import { directVolume_frag } from './shader/direct-volume.frag';
@@ -185,11 +209,9 @@ function getDefinesCode(defines: ShaderDefines) {
}
function getGlsl100FragPrefix(extensions: WebGLExtensions, shaderExtensions: ShaderExtensions) {
const prefix: string[] = [];
if (shaderExtensions.standardDerivatives) {
prefix.push('#extension GL_OES_standard_derivatives : enable');
prefix.push('#define enabledStandardDerivatives');
}
const prefix: string[] = [
'#extension GL_OES_standard_derivatives : enable'
];
if (shaderExtensions.fragDepth) {
if (extensions.fragDepth) {
prefix.push('#extension GL_EXT_frag_depth : enable');
@@ -244,9 +266,6 @@ function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExten
`layout(location = 0) out highp ${outTypes[0] || 'vec4'} out_FragData0;`
];
if (shaderExtensions.standardDerivatives) {
prefix.push('#define enabledStandardDerivatives');
}
if (shaderExtensions.fragDepth) {
prefix.push('#define enabledFragDepth');
}
@@ -278,8 +297,8 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens
return {
id: shaderCodeId(),
name: shaders.name,
vert: `${vertPrefix}${header}${shaders.vert}`,
frag: `${fragPrefix}${header}${frag}`,
vert: `${vertPrefix}${header}${preprocess(shaders.vert, defines)}`,
frag: `${fragPrefix}${header}${preprocess(frag, defines)}`,
extensions: shaders.extensions,
outTypes: shaders.outTypes
};

View File

@@ -1,10 +1,10 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* adapted from three.js (https://github.com/mrdoob/three.js/)
* which under the MIT License, Copyright © 2010-2019 three.js authors
* which under the MIT License, Copyright © 2010-2021 three.js authors
*/
export const apply_light_color = `
@@ -14,21 +14,21 @@ export const apply_light_color = `
// - vec3 normal
// - float uMetalness
// - float uRoughness
// - float uReflectivity
// - float uLightIntensity
// - float uAmbientIntensity
// - vec3 uLightColor
// - vec3 uAmbientColor
// outputs
// - sets gl_FragColor
vec4 color = material;
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0));
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
PhysicalMaterial physicalMaterial;
physicalMaterial.diffuseColor = color.rgb * (1.0 - uMetalness);
physicalMaterial.specularRoughness = clamp(uRoughness, 0.04, 1.0);
physicalMaterial.specularColor = mix(vec3(0.16 * pow2(uReflectivity)), color.rgb, uMetalness);
physicalMaterial.diffuseColor = color.rgb * (1.0 - metalness);
physicalMaterial.roughness = max(roughness, 0.0525);
physicalMaterial.specularColor = mix(vec3(0.04), color.rgb, metalness);
physicalMaterial.specularF90 = 1.0;
GeometricContext geometry;
geometry.position = -vViewPosition;
@@ -36,19 +36,28 @@ geometry.normal = normal;
geometry.viewDir = normalize(vViewPosition);
IncidentLight directLight;
directLight.direction = vec3(0.0, 0.0, -1.0);
directLight.color = vec3(uLightIntensity);
#pragma unroll_loop_start
for (int i = 0; i < dLightCount; ++i) {
directLight.direction = uLightDirection[i];
directLight.color = uLightColor[i] * PI; // * PI for punctual light
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
}
#pragma unroll_loop_end
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
vec3 irradiance = vec3(uAmbientIntensity) * PI;
vec3 irradiance = uAmbientColor * PI; // * PI for punctual light
RE_IndirectDiffuse_Physical(irradiance, geometry, physicalMaterial, reflectedLight);
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular;
// indirect specular only metals
vec3 radiance = uAmbientColor * metalness;
vec3 iblIrradiance = uAmbientColor * metalness;
vec3 clearcoatRadiance = vec3(0.0);
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
gl_FragColor = vec4(outgoingLight, color.a);
#ifdef dXrayShaded
gl_FragColor.a *= 1.0 - pow(abs(dot(normal, vec3(0, 0, 1))), uXrayEdgeFalloff);
gl_FragColor.a *= 1.0 - pow(abs(dot(normal, vec3(0.0, 0.0, 1.0))), uXrayEdgeFalloff);
#endif
`;

View File

@@ -41,6 +41,20 @@ export const assign_color_varying = `
vOverpaint.rgb = mix(vColor.rgb, vOverpaint.rgb, vOverpaint.a);
#endif
#endif
#ifdef dSubstance
#if defined(dSubstanceType_groupInstance)
vSubstance = readFromTexture(tSubstance, aInstance * float(uGroupCount) + group, uSubstanceTexDim).rgb;
#elif defined(dSubstanceType_vertexInstance)
vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + VertexID, uSubstanceTexDim).rgb;
#elif defined(dSubstanceType_volumeInstance)
vec3 sgridPos = (uSubstanceGridTransform.w * (vModelPosition - uSubstanceGridTransform.xyz)) / uSubstanceGridDim;
vSubstance = texture3dFrom2dLinear(tSubstanceGrid, sgridPos, uSubstanceGridDim, uSubstanceTexDim).rgb;
#endif
// pre-mix to avoid artifacts due to empty substance
vSubstance.rg = mix(vec2(uMetalness, uRoughness), vSubstance.rg, vSubstance.b);
#endif
#elif defined(dRenderVariant_pick)
#if defined(dRenderVariant_pickObject)
vColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);

View File

@@ -20,6 +20,13 @@ export const assign_material_color = `
#if defined(dOverpaint)
material.rgb = mix(material.rgb, vOverpaint.rgb, vOverpaint.a);
#endif
float metalness = uMetalness;
float roughness = uRoughness;
#ifdef dSubstance
metalness = mix(metalness, vSubstance.r, vSubstance.b);
roughness = mix(roughness, vSubstance.g, vSubstance.b);
#endif
#elif defined(dRenderVariant_pick)
vec4 material = vColor;
#elif defined(dRenderVariant_depth)

View File

@@ -1,4 +1,7 @@
export const color_frag_params = `
uniform float uMetalness;
uniform float uRoughness;
#if defined(dRenderVariant_color)
#if defined(dColorType_uniform)
uniform vec3 uColor;
@@ -9,6 +12,10 @@ export const color_frag_params = `
#ifdef dOverpaint
varying vec4 vOverpaint;
#endif
#ifdef dSubstance
varying vec3 vSubstance;
#endif
#elif defined(dRenderVariant_pick)
#if __VERSION__ == 100
varying vec4 vColor;

View File

@@ -1,4 +1,7 @@
export const color_vert_params = `
uniform float uMetalness;
uniform float uRoughness;
#if defined(dRenderVariant_color)
#if defined(dColorType_uniform)
uniform vec3 uColor;
@@ -30,6 +33,20 @@ export const color_vert_params = `
uniform sampler2D tOverpaintGrid;
#endif
#endif
#ifdef dSubstance
#if defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance)
varying vec3 vSubstance;
uniform vec2 uSubstanceTexDim;
uniform sampler2D tSubstance;
#elif defined(dSubstanceType_volumeInstance)
varying vec3 vSubstance;
uniform vec2 uSubstanceTexDim;
uniform vec3 uSubstanceGridDim;
uniform vec4 uSubstanceGridTransform;
uniform sampler2D tSubstanceGrid;
#endif
#endif
#elif defined(dRenderVariant_pick)
#if __VERSION__ == 100
varying vec4 vColor;

View File

@@ -89,8 +89,9 @@ export const common_clip = `
// flag is a bit-flag for clip-objects to ignore (note, object ids start at 1 not 0)
bool clipTest(vec4 sphere, int flag) {
#pragma unroll_loop_start
for (int i = 0; i < dClipObjectCount; ++i) {
if (flag == 0 || hasBit(flag, i + 1)) {
if (flag == 0 || hasBit(flag, UNROLLED_LOOP_INDEX + 1)) {
// TODO take sphere radius into account?
bool test = getSignedDistance(sphere.xyz, uClipObjectType[i], uClipObjectPosition[i], uClipObjectRotation[i], uClipObjectScale[i]) <= 0.0;
if ((!uClipObjectInvert[i] && test) || (uClipObjectInvert[i] && !test)) {
@@ -98,6 +99,7 @@ export const common_clip = `
}
}
}
#pragma unroll_loop_end
return false;
}
#endif

View File

@@ -1,23 +1,22 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* adapted from three.js (https://github.com/mrdoob/three.js/)
* which under the MIT License, Copyright © 2010-2019 three.js authors
* which under the MIT License, Copyright © 2010-2021 three.js authors
*/
export const light_frag_params = `
uniform float uLightIntensity;
uniform float uAmbientIntensity;
uniform float uReflectivity;
uniform float uMetalness;
uniform float uRoughness;
uniform vec3 uLightDirection[dLightCount];
uniform vec3 uLightColor[dLightCount];
uniform vec3 uAmbientColor;
struct PhysicalMaterial {
vec3 diffuseColor;
float specularRoughness;
float roughness;
vec3 specularColor;
float specularF90;
};
struct IncidentLight {
@@ -29,6 +28,7 @@ struct ReflectedLight {
vec3 directDiffuse;
vec3 directSpecular;
vec3 indirectDiffuse;
vec3 indirectSpecular;
};
struct GeometricContext {
@@ -37,20 +37,23 @@ struct GeometricContext {
vec3 viewDir;
};
vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {
vec3 BRDF_Lambert(const in vec3 diffuseColor) {
return RECIPROCAL_PI * diffuseColor;
}
vec3 F_Schlick(const in vec3 f0, const in float f90, const in float dotVH) {
// Original approximation by Christophe Schlick '94
// float fresnel = pow( 1.0 - dotLH, 5.0 );
// float fresnel = pow( 1.0 - dotVH, 5.0 );
// Optimized variant (presented by Epic at SIGGRAPH '13)
// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
float fresnel = exp2((-5.55473 * dotLH - 6.98316) * dotLH);
return (1.0 - specularColor) * fresnel + specularColor;
float fresnel = exp2((-5.55473 * dotVH - 6.98316) * dotVH);
return f0 * (1.0 - fresnel) + (f90 * fresnel);
}
// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
float G_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {
float V_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {
float a2 = pow2(alpha);
// dotNL and dotNV are explicitly swapped. This is not a mistake.
float gv = dotNL * sqrt(a2 + (1.0 - a2) * pow2(dotNV));
float gl = dotNV * sqrt(a2 + (1.0 - a2) * pow2(dotNL));
return 0.5 / max(gv + gl, EPSILON);
@@ -65,47 +68,68 @@ float D_GGX(const in float alpha, const in float dotNH) {
return RECIPROCAL_PI * a2 / pow2(denom);
}
vec3 BRDF_Diffuse_Lambert(const in vec3 diffuseColor) {
return RECIPROCAL_PI * diffuseColor;
}
// GGX Distribution, Schlick Fresnel, GGX-Smith Visibility
vec3 BRDF_Specular_GGX(const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness) {
// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
vec3 BRDF_GGX(const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 f0, const in float f90, const in float roughness) {
float alpha = pow2(roughness); // UE4's roughness
vec3 halfDir = normalize(incidentLight.direction + geometry.viewDir);
float dotNL = saturate(dot(geometry.normal, incidentLight.direction));
float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
float dotNH = saturate(dot(geometry.normal, halfDir));
float dotLH = saturate(dot(incidentLight.direction, halfDir));
vec3 F = F_Schlick(specularColor, dotLH);
float G = G_GGX_SmithCorrelated(alpha, dotNL, dotNV);
vec3 halfDir = normalize( lightDir + viewDir);
float dotNL = saturate(dot(normal, lightDir));
float dotNV = saturate(dot(normal, viewDir));
float dotNH = saturate(dot(normal, halfDir));
float dotVH = saturate(dot(viewDir, halfDir));
vec3 F = F_Schlick(f0, f90, dotVH);
float V = V_GGX_SmithCorrelated(alpha, dotNL, dotNV);
float D = D_GGX(alpha, dotNH);
return F * (G * D);
return F * (V * D);
}
// ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile
vec3 BRDF_Specular_GGX_Environment(const in GeometricContext geometry, const in vec3 specularColor, const in float roughness) {
float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
// Analytical approximation of the DFG LUT, one half of the
// split-sum approximation used in indirect specular lighting.
// via 'environmentBRDF' from "Physically Based Shading on Mobile"
// https://www.unrealengine.com/blog/physically-based-shading-on-mobile
vec2 DFGApprox(const in vec3 normal, const in vec3 viewDir, const in float roughness) {
float dotNV = saturate(dot(normal, viewDir));
const vec4 c0 = vec4(-1, -0.0275, -0.572, 0.022);
const vec4 c1 = vec4(1, 0.0425, 1.04, -0.04);
vec4 r = roughness * c0 + c1;
float a004 = min(r.x * r.x, exp2(-9.28 * dotNV)) * r.x + r.y;
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
return specularColor * AB.x + AB.y;
vec2 fab = vec2(-1.04, 1.04) * a004 + r.zw;
return fab;
}
// Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
// Approximates multiscattering in order to preserve energy.
// http://www.jcgt.org/published/0008/01/03/
void computeMultiscattering(const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter) {
vec2 fab = DFGApprox(normal, viewDir, roughness);
vec3 FssEss = specularColor * fab.x + specularF90 * fab.y;
float Ess = fab.x + fab.y;
float Ems = 1.0 - Ess;
vec3 Favg = specularColor + (1.0 - specularColor) * 0.047619; // 1/21
vec3 Fms = FssEss * Favg / (1.0 - Ems * Favg);
singleScatter += FssEss;
multiScatter += Fms * Ems;
}
void RE_Direct_Physical(const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
float dotNL = saturate(dot(geometry.normal, directLight.direction));
vec3 irradiance = dotNL * directLight.color;
irradiance *= PI; // punctual light
reflectedLight.directSpecular += irradiance * BRDF_Specular_GGX(directLight, geometry, material.specularColor, material.specularRoughness);
reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
reflectedLight.directSpecular += irradiance * BRDF_GGX(directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness);
reflectedLight.directDiffuse += irradiance * BRDF_Lambert(material.diffuseColor);
}
void RE_IndirectDiffuse_Physical(const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert(material.diffuseColor);
}
void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
// Both indirect specular and indirect diffuse light accumulate here
vec3 singleScattering = vec3(0.0);
vec3 multiScattering = vec3(0.0);
vec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;
computeMultiscattering(geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering);
vec3 diffuse = material.diffuseColor * (1.0 - ( singleScattering + multiScattering));
reflectedLight.indirectSpecular += radiance * singleScattering;
reflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;
reflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;
}
`;

View File

@@ -64,6 +64,9 @@ uniform int uMarkerPriority;
uniform sampler2D tMarker;
#endif
uniform float uMetalness;
uniform float uRoughness;
uniform float uFogNear;
uniform float uFogFar;
uniform vec3 uFogColor;
@@ -115,6 +118,13 @@ uniform mat4 uCartnToUnit;
uniform sampler2D tOverpaint;
#endif
#endif
#ifdef dSubstance
#if defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance)
uniform vec2 uSubstanceTexDim;
uniform sampler2D tSubstance;
#endif
#endif
#endif
#if defined(dGridTexType_2d)
@@ -194,6 +204,9 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
vec3 color = vec3(0.45, 0.55, 0.8);
vec4 overpaint = vec4(0.0);
vec3 substance = vec3(0.0);
float metalness = uMetalness;
float roughness = uRoughness;
vec3 gradient = vec3(1.0);
vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0);
@@ -304,7 +317,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#if defined(dOverpaintType_groupInstance)
overpaint = readFromTexture(tOverpaint, vInstance * float(uGroupCount) + group, uOverpaintTexDim);
#elif defined(dOverpaintType_vertexInstance)
overpaint = texture3dFrom1dTrilinear(tOverpaint, isoPos, uGridDim, uOverpaintTexDim, vInstance * float(uVertexCount)).rgb;
overpaint = texture3dFrom1dTrilinear(tOverpaint, isoPos, uGridDim, uOverpaintTexDim, vInstance * float(uVertexCount));
#endif
color = mix(color, overpaint.rgb, overpaint.a);
@@ -345,6 +358,15 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
vec3 normal = -normalize(normalMatrix * normalize(gradient));
normal = normal * (float(flipped) * 2.0 - 1.0);
normal = normal * -(float(interior) * 2.0 - 1.0);
#ifdef dSubstance
#if defined(dSubstanceType_groupInstance)
substance = readFromTexture(tSubstance, vInstance * float(uGroupCount) + group, uSubstanceTexDim).rgb;
#elif defined(dSubstanceType_vertexInstance)
substance = texture3dFrom1dTrilinear(tSubstance, isoPos, uGridDim, uSubstanceTexDim, vInstance * float(uVertexCount)).rgb;
#endif
metalness = mix(metalness, substance.r, substance.b);
roughness = mix(roughness, substance.g, substance.b);
#endif
#include apply_light_color
#endif

View File

@@ -19,14 +19,10 @@ void main() {
#include clip_pixel
// Workaround for buggy gl_FrontFacing (e.g. on some integrated Intel GPUs)
#if defined(enabledStandardDerivatives)
vec3 fdx = dFdx(vViewPosition);
vec3 fdy = dFdy(vViewPosition);
vec3 faceNormal = normalize(cross(fdx,fdy));
bool frontFacing = dot(vNormal, faceNormal) > 0.0;
#else
bool frontFacing = dot(vNormal, vViewPosition) < 0.0;
#endif
vec3 fdx = dFdx(vViewPosition);
vec3 fdy = dFdy(vViewPosition);
vec3 faceNormal = normalize(cross(fdx,fdy));
bool frontFacing = dot(vNormal, faceNormal) > 0.0;
#if defined(dFlipSided)
interior = frontFacing;
@@ -48,7 +44,7 @@ void main() {
#ifdef dIgnoreLight
gl_FragColor = material;
#else
#if defined(dFlatShaded) && defined(enabledStandardDerivatives)
#if defined(dFlatShaded)
vec3 normal = -faceNormal;
#else
vec3 normal = -normalize(vNormal);

View File

@@ -36,13 +36,11 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
if (elementIndexUint === null) {
throw new Error('Could not find support for "element_index_uint"');
}
const standardDerivatives = getStandardDerivatives(gl);
if (isDebugMode && standardDerivatives === null) {
// - non-support handled downstream (flat shading option is ignored)
// - can't be a required extension because it is not supported by `headless-gl`
console.log('Could not find support for "standard_derivatives"');
if (standardDerivatives === null) {
throw new Error('Could not find support for "standard_derivatives"');
}
const textureFloat = getTextureFloat(gl);
if (isDebugMode && textureFloat === null) {
console.log('Could not find support for "texture_float"');

View File

@@ -504,6 +504,19 @@ namespace Vec3 {
return dot(tmp_dh_cb, tmp_dh_cross) > 0 ? _angle : -_angle;
}
/**
* @param inclination in radians [0, PI]
* @param azimuth in radians [0, 2 * PI]
* @param radius [0, +Inf]
*/
export function directionFromSpherical(out: Vec3, inclination: number, azimuth: number, radius: number): Vec3 {
return Vec3.set(out,
radius * Math.cos(azimuth) * Math.sin(inclination),
radius * Math.sin(azimuth) * Math.sin(inclination),
radius * Math.cos(inclination)
);
}
/**
* Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
*/

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Fred Ludlow <Fred.Ludlow@astx.com>
@@ -68,11 +68,8 @@ function addUnitHydrogenDonors(structure: Structure, unit: Unit.Atomic, builder:
const { totalH } = getUnitValenceModel(structure, unit);
const { elements } = unit;
const { x, y, z } = unit.model.atomicConformation;
const { elementAromaticRingIndices } = unit.rings;
for (let i = 0 as StructureElement.UnitIndex, il = elements.length; i < il; ++i) {
if (elementAromaticRingIndices.has(i)) continue;
const element = typeSymbol(unit, i);
if ((
// include both nitrogen atoms in histidine due to
@@ -134,15 +131,12 @@ function addUnitHydrogenAcceptors(structure: Structure, unit: Unit.Atomic, build
const { charge, implicitH, idealGeometry } = getUnitValenceModel(structure, unit);
const { elements } = unit;
const { x, y, z } = unit.model.atomicConformation;
const { elementAromaticRingIndices } = unit.rings;
const add = (i: StructureElement.UnitIndex) => {
builder.add(FeatureType.HydrogenAcceptor, FeatureGroup.None, x[elements[i]], y[elements[i]], z[elements[i]], i);
};
for (let i = 0 as StructureElement.UnitIndex, il = elements.length; i < il; ++i) {
if (elementAromaticRingIndices.has(i)) continue;
const element = typeSymbol(unit, i);
if (element === Elements.O) {
// Basically assume all oxygen atoms are acceptors!

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Structure, StructureElement } from '../../mol-model/structure';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { PluginContext } from '../../mol-plugin/context';
import { StateBuilder, StateObjectCell, StateSelection, StateTransform } from '../../mol-state';
import { Substance } from '../../mol-theme/substance';
import { StructureComponentRef } from '../manager/structure/hierarchy-state';
import { EmptyLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
import { Material } from '../../mol-util/material';
type SubstanceEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, substance?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.SubstanceStructureRepresentation3DFromBundle>>) => Promise<void>
const SubstanceManagerTag = 'substance-controls';
export async function setStructureSubstance(plugin: PluginContext, components: StructureComponentRef[], material: Material | undefined, lociGetter: (structure: Structure) => Promise<StructureElement.Loci | EmptyLoci>, types?: string[]) {
await eachRepr(plugin, components, async (update, repr, substanceCell) => {
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
const structure = repr.obj!.data.sourceData;
// always use the root structure to get the loci so the substance
// stays applicable as long as the root structure does not change
const loci = await lociGetter(structure.root);
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) return;
const layer = {
bundle: StructureElement.Bundle.fromLoci(loci),
material: material ?? Material(),
clear: !material
};
if (substanceCell) {
const bundleLayers = [...substanceCell.params!.values.layers, layer];
const filtered = getFilteredBundle(bundleLayers, structure);
update.to(substanceCell).update(Substance.toBundle(filtered));
} else {
const filtered = getFilteredBundle([layer], structure);
update.to(repr.transform.ref)
.apply(StateTransforms.Representation.SubstanceStructureRepresentation3DFromBundle, Substance.toBundle(filtered), { tags: SubstanceManagerTag });
}
});
}
export async function clearStructureSubstance(plugin: PluginContext, components: StructureComponentRef[], types?: string[]) {
await eachRepr(plugin, components, async (update, repr, substanceCell) => {
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
if (substanceCell) {
update.delete(substanceCell.transform.ref);
}
});
}
async function eachRepr(plugin: PluginContext, components: StructureComponentRef[], callback: SubstanceEachReprCallback) {
const state = plugin.state.data;
const update = state.build();
for (const c of components) {
for (const r of c.representations) {
const substance = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.SubstanceStructureRepresentation3DFromBundle, r.cell.transform.ref).withTag(SubstanceManagerTag));
await callback(update, r.cell, substance[0]);
}
}
return update.commit({ doNotUpdateCurrent: true });
}
/** filter substance layers for given structure */
function getFilteredBundle(layers: Substance.BundleLayer[], structure: Structure) {
const substance = Substance.ofBundle(layers, structure.root);
const merged = Substance.merge(substance);
return Substance.filter(merged, structure);
}

View File

@@ -30,6 +30,8 @@ import { Clipping } from '../../../mol-theme/clipping';
import { setStructureClipping } from '../../helpers/structure-clipping';
import { setStructureTransparency } from '../../helpers/structure-transparency';
import { StructureFocusRepresentation } from '../../../mol-plugin/behavior/dynamic/selection/structure-focus-representation';
import { setStructureSubstance } from '../../helpers/structure-substance';
import { Material } from '../../../mol-util/material';
export { StructureComponentManager };
@@ -67,22 +69,24 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
await update.commit();
await this.plugin.state.updateBehavior(StructureFocusRepresentation, p => {
p.ignoreHydrogens = !options.showHydrogens;
p.material = options.materialStyle;
});
if (interactionChanged) await this.updateInterationProps();
});
}
private updateReprParams(update: StateBuilder.Root, component: StructureComponentRef) {
const { showHydrogens, visualQuality: quality } = this.state.options;
const { showHydrogens, visualQuality: quality, materialStyle: material } = this.state.options;
const ignoreHydrogens = !showHydrogens;
for (const r of component.representations) {
if (r.cell.transform.transformer !== StructureRepresentation3D) continue;
const params = r.cell.transform.params as StateTransformer.Params<StructureRepresentation3D>;
if (!!params.type.params.ignoreHydrogens !== ignoreHydrogens || params.type.params.quality !== quality) {
if (!!params.type.params.ignoreHydrogens !== ignoreHydrogens || params.type.params.quality !== quality || params.type.params.material !== material) {
update.to(r.cell).update(old => {
old.type.params.ignoreHydrogens = ignoreHydrogens;
old.type.params.quality = quality;
old.type.params.material = material;
});
}
}
@@ -301,9 +305,9 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
addRepresentation(components: ReadonlyArray<StructureComponentRef>, type: string) {
if (components.length === 0) return;
const { showHydrogens, visualQuality: quality } = this.state.options;
const { showHydrogens, visualQuality: quality, materialStyle: material } = this.state.options;
const ignoreHydrogens = !showHydrogens;
const typeParams = { ignoreHydrogens, quality };
const typeParams = { ignoreHydrogens, quality, material };
return this.plugin.dataTransaction(async () => {
for (const component of components) {
@@ -338,9 +342,9 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
const xs = structures || this.currentStructures;
if (xs.length === 0) return;
const { showHydrogens, visualQuality: quality } = this.state.options;
const { showHydrogens, visualQuality: quality, materialStyle: material } = this.state.options;
const ignoreHydrogens = !showHydrogens;
const typeParams = { ignoreHydrogens, quality };
const typeParams = { ignoreHydrogens, quality, material };
const componentKey = UUID.create22();
for (const s of xs) {
@@ -372,14 +376,19 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
const getLoci = async (s: Structure) => StructureSelection.toLociWithSourceUnits(await params.selection.getSelection(this.plugin, ctx, s));
for (const s of xs) {
if (params.action.name === 'reset') {
await setStructureOverpaint(this.plugin, s.components, -1, getLoci, params.representations);
} else if (params.action.name === 'color') {
if (params.action.name === 'color') {
const p = params.action.params;
await setStructureOverpaint(this.plugin, s.components, p.color, getLoci, params.representations);
} else if (params.action.name === 'resetColor') {
await setStructureOverpaint(this.plugin, s.components, -1, getLoci, params.representations);
} else if (params.action.name === 'transparency') {
const p = params.action.params;
await setStructureTransparency(this.plugin, s.components, p.value, getLoci, params.representations);
} else if (params.action.name === 'material') {
const p = params.action.params;
await setStructureSubstance(this.plugin, s.components, p.material, getLoci, params.representations);
} else if (params.action.name === 'resetMaterial') {
await setStructureSubstance(this.plugin, s.components, void 0, getLoci, params.representations);
} else if (params.action.name === 'clipping') {
const p = params.action.params;
await setStructureClipping(this.plugin, s.components, Clipping.Groups.fromNames(p.excludeGroups), getLoci, params.representations);
@@ -445,6 +454,7 @@ namespace StructureComponentManager {
export const OptionsParams = {
showHydrogens: PD.Boolean(true, { description: 'Toggle display of hydrogen atoms in representations' }),
visualQuality: PD.Select('auto', VisualQualityOptions, { description: 'Control the visual/rendering quality of representations' }),
materialStyle: Material.getParam(),
interactions: PD.Group(InteractionsProvider.defaultParams, { label: 'Non-covalent Interactions' }),
};
export type Options = PD.Values<typeof OptionsParams>
@@ -477,10 +487,14 @@ namespace StructureComponentManager {
color: PD.Group({
color: PD.Color(ColorNames.blue, { isExpanded: true }),
}, { isFlat: true }),
reset: PD.EmptyGroup({ label: 'Reset Color' }),
resetColor: PD.EmptyGroup({ label: 'Reset Color' }),
transparency: PD.Group({
value: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
}, { isFlat: true }),
material: PD.Group({
material: Material.getParam({ isFlat: true }),
}, { isFlat: true }),
resetMaterial: PD.EmptyGroup({ label: 'Reset Material' }),
clipping: PD.Group({
excludeGroups: PD.MultiSelect([] as Clipping.Groups.Names[], PD.objectToOptions(Clipping.Groups.Names)),
}, { isFlat: true }),

View File

@@ -41,6 +41,8 @@ import { getBoxMesh } from './shape';
import { Shape } from '../../mol-model/shape';
import { Box3D } from '../../mol-math/geometry';
import { PlaneParams, PlaneRepresentation } from '../../mol-repr/shape/loci/plane';
import { Substance } from '../../mol-theme/substance';
import { Material } from '../../mol-util/material';
export { StructureRepresentation3D };
export { ExplodeStructureRepresentation3D };
@@ -50,6 +52,8 @@ export { OverpaintStructureRepresentation3DFromScript };
export { OverpaintStructureRepresentation3DFromBundle };
export { TransparencyStructureRepresentation3DFromScript };
export { TransparencyStructureRepresentation3DFromBundle };
export { SubstanceStructureRepresentation3DFromScript };
export { SubstanceStructureRepresentation3DFromBundle };
export { ClippingStructureRepresentation3DFromScript };
export { ClippingStructureRepresentation3DFromBundle };
export { VolumeRepresentation3D };
@@ -529,6 +533,121 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
}
});
type SubstanceStructureRepresentation3DFromScript = typeof SubstanceStructureRepresentation3DFromScript
const SubstanceStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn({
name: 'substance-structure-representation-3d-from-script',
display: 'Substance 3D Representation',
from: SO.Molecule.Structure.Representation3D,
to: SO.Molecule.Structure.Representation3DState,
params: () => ({
layers: PD.ObjectList({
script: PD.Script(Script('(sel.atom.all)', 'mol-script')),
material: Material.getParam(),
clear: PD.Boolean(false)
}, e => `${e.clear ? 'Clear' : Material.toString(e.material)}`, {
defaultValue: [{
script: Script('(sel.atom.all)', 'mol-script'),
material: Material({ roughness: 1 }),
clear: false
}]
}),
})
})({
canAutoUpdate() {
return true;
},
apply({ a, params }) {
const structure = a.data.sourceData;
const geometryVersion = a.data.repr.geometryVersion;
const substance = Substance.ofScript(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { substance },
initialState: { substance: Substance.Empty },
info: { structure, geometryVersion },
repr: a.data.repr
}, { label: `Substance (${substance.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const info = b.data.info as { structure: Structure, geometryVersion: number };
const newStructure = a.data.sourceData;
if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const newGeometryVersion = a.data.repr.geometryVersion;
// smoothing needs to be re-calculated when geometry changes
if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
const oldSubstance = b.data.state.substance!;
const newSubstance = Substance.ofScript(newParams.layers, newStructure);
if (Substance.areEqual(oldSubstance, newSubstance)) return StateTransformer.UpdateResult.Unchanged;
info.geometryVersion = newGeometryVersion;
b.data.state.substance = newSubstance;
b.data.repr = a.data.repr;
b.label = `Substance (${newSubstance.layers.length} Layers)`;
return StateTransformer.UpdateResult.Updated;
}
});
type SubstanceStructureRepresentation3DFromBundle = typeof SubstanceStructureRepresentation3DFromBundle
const SubstanceStructureRepresentation3DFromBundle = PluginStateTransform.BuiltIn({
name: 'substance-structure-representation-3d-from-bundle',
display: 'Substance 3D Representation',
from: SO.Molecule.Structure.Representation3D,
to: SO.Molecule.Structure.Representation3DState,
params: () => ({
layers: PD.ObjectList({
bundle: PD.Value<StructureElement.Bundle>(StructureElement.Bundle.Empty),
material: Material.getParam(),
clear: PD.Boolean(false)
}, e => `${e.clear ? 'Clear' : Material.toString(e.material)}`, {
defaultValue: [{
bundle: StructureElement.Bundle.Empty,
material: Material({ roughness: 1 }),
clear: false
}],
isHidden: true
}),
})
})({
canAutoUpdate() {
return true;
},
apply({ a, params }) {
const structure = a.data.sourceData;
const geometryVersion = a.data.repr.geometryVersion;
const substance = Substance.ofBundle(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { substance },
initialState: { substance: Substance.Empty },
info: { structure, geometryVersion },
repr: a.data.repr
}, { label: `Substance (${substance.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const info = b.data.info as { structure: Structure, geometryVersion: number };
const newStructure = a.data.sourceData;
if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const newGeometryVersion = a.data.repr.geometryVersion;
// smoothing needs to be re-calculated when geometry changes
if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
const oldSubstance = b.data.state.substance!;
const newSubstance = Substance.ofBundle(newParams.layers, newStructure);
if (Substance.areEqual(oldSubstance, newSubstance)) return StateTransformer.UpdateResult.Unchanged;
info.geometryVersion = newGeometryVersion;
b.data.state.substance = newSubstance;
b.data.repr = a.data.repr;
b.label = `Substance (${newSubstance.layers.length} Layers)`;
return StateTransformer.UpdateResult.Updated;
}
});
type ClippingStructureRepresentation3DFromScript = typeof ClippingStructureRepresentation3DFromScript
const ClippingStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn({
name: 'clipping-structure-representation-3d-from-script',

View File

@@ -19,7 +19,7 @@ import { PluginUIComponent } from '../base';
import { ActionMenu } from './action-menu';
import { ColorOptions, ColorValueOption, CombinedColorControl } from './color';
import { Button, ControlGroup, ControlRow, ExpandGroup, IconButton, TextInput, ToggleButton } from './common';
import { Icon, HelpOutlineSvg, CheckSvg, ClearSvg, BookmarksOutlinedSvg, MoreHorizSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowDownwardSvg, ArrowUpwardSvg, DeleteOutlinedSvg } from './icons';
import { Icon, HelpOutlineSvg, CheckSvg, ClearSvg, BookmarksOutlinedSvg, MoreHorizSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowDownwardSvg, ArrowUpwardSvg, DeleteOutlinedSvg, TuneSvg } from './icons';
import { legendFor } from './legend';
import { LineGraphComponent } from './line-graph/line-graph-component';
import { Slider, Slider2 } from './slider';
@@ -1079,8 +1079,8 @@ export class MultiSelectControl extends React.PureComponent<ParamProps<PD.MultiS
}
}
export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>> & { inMapped?: boolean }, { isExpanded: boolean, showHelp: boolean }> {
state = { isExpanded: !!this.props.param.isExpanded, showHelp: false }
export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>> & { inMapped?: boolean }, { isExpanded: boolean, showPresets: boolean, showHelp: boolean }> {
state = { isExpanded: !!this.props.param.isExpanded, showPresets: false, showHelp: false }
change(value: any) {
this.props.onChange({ name: this.props.name, param: this.props.param, value });
@@ -1091,6 +1091,29 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
}
toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
toggleShowPresets = () => this.setState({ showPresets: !this.state.showPresets });
presetItems = memoizeLatest((param: PD.Group<any>) => ActionMenu.createItemsFromSelectOptions(param.presets ?? []));
onSelectPreset: ActionMenu.OnSelect = item => {
this.setState({ showPresets: false });
this.change(item?.value);
}
presets() {
if (!this.props.param.presets) return null;
const label = this.props.param.label || camelCaseToWords(this.props.name);
return <div className='msp-control-group-wrapper'>
<div className='msp-control-group-header'>
<button className='msp-btn msp-form-control msp-btn-block' onClick={this.toggleShowPresets}>
<Icon svg={TuneSvg} />
{label} Presets
</button>
</div>
{this.state.showPresets && <ActionMenu items={this.presetItems(this.props.param)} onSelect={this.onSelectPreset} />}
</div>;
}
pivoted() {
const key = this.props.param.pivot as string;
@@ -1116,6 +1139,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
{ctrl}
<IconButton svg={MoreHorizSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`More Options`} />
<div className='msp-control-offset'>
{this.presets()}
<ParameterControls params={filtered} onEnter={this.props.onEnter} values={this.props.value} onChange={this.onChangeParam} isDisabled={this.props.isDisabled} />
</div>
</div>;
@@ -1149,6 +1173,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
</button>
</div>
{this.state.isExpanded && <div className='msp-control-offset'>
{this.presets()}
{controls}
</div>}
</div>;

View File

@@ -56,11 +56,10 @@ const SimpleSettingsParams = {
transparent: PD.Boolean(false)
}, { pivot: 'color' }),
lighting: PD.Group({
renderStyle: Canvas3DParams.renderer.params.style,
occlusion: Canvas3DParams.postprocessing.params.occlusion,
outline: Canvas3DParams.postprocessing.params.outline,
fog: Canvas3DParams.cameraFog,
}, { pivot: 'renderStyle' }),
}, { isFlat: true }),
clipping: PD.Group<any>({
...Canvas3DParams.cameraClipping.params,
...(Canvas3DParams.renderer.params.clip as any).params as any
@@ -105,10 +104,9 @@ const SimpleSettingsMapping = ParamMapping({
transparent: canvas.transparentBackground
},
lighting: {
renderStyle: renderer.style,
occlusion: canvas.postprocessing.occlusion,
outline: canvas.postprocessing.outline,
fog: canvas.cameraFog
fog: canvas.cameraFog,
},
clipping: {
...canvas.cameraClipping,
@@ -123,7 +121,6 @@ const SimpleSettingsMapping = ParamMapping({
canvas.camera = s.camera;
canvas.transparentBackground = s.background.transparent;
canvas.renderer.backgroundColor = s.background.color;
canvas.renderer.style = s.lighting.renderStyle;
canvas.postprocessing.occlusion = s.lighting.occlusion;
canvas.postprocessing.outline = s.lighting.outline;
canvas.cameraFog = s.lighting.fog;

View File

@@ -18,6 +18,7 @@ import { SizeTheme } from '../../../../mol-theme/size';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { PluginCommands } from '../../../commands';
import { PluginContext } from '../../../context';
import { Material } from '../../../../mol-util/material';
const StructureFocusRepresentationParams = (plugin: PluginContext) => {
const reprParams = StateTransforms.Representation.StructureRepresentation3D.definition.params!(void 0, plugin) as PD.Params;
@@ -41,7 +42,8 @@ const StructureFocusRepresentationParams = (plugin: PluginContext) => {
}),
components: PD.MultiSelect(FocusComponents, PD.arrayToOptions(FocusComponents)),
excludeTargetFromSurroundings: PD.Boolean(false, { label: 'Exclude Target', description: 'Exclude the focus "target" from the surroudings component.' }),
ignoreHydrogens: PD.Boolean(false)
ignoreHydrogens: PD.Boolean(false),
material: Material.getParam(),
};
};
@@ -67,7 +69,7 @@ class StructureFocusRepresentationBehavior extends PluginBehavior.WithSubscriber
...this.params.targetParams,
type: {
name: reprParams.type.name,
params: { ...reprParams.type.params, ignoreHydrogens: this.params.ignoreHydrogens }
params: { ...reprParams.type.params, ignoreHydrogens: this.params.ignoreHydrogens, material: this.params.material }
}
};
}

View File

@@ -101,6 +101,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
PluginSpec.Action(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D),
PluginSpec.Action(StateTransforms.Representation.OverpaintStructureRepresentation3DFromScript),
PluginSpec.Action(StateTransforms.Representation.TransparencyStructureRepresentation3DFromScript),
PluginSpec.Action(StateTransforms.Representation.SubstanceStructureRepresentation3DFromScript),
PluginSpec.Action(AssignColorVolume),
PluginSpec.Action(StateTransforms.Volume.VolumeFromCcp4),

View File

@@ -20,6 +20,7 @@ import { merge } from 'rxjs';
import { PluginContext } from './context';
import { PluginComponent } from '../mol-plugin-state/component';
import { PluginConfig } from './config';
import { StructureComponentManager } from '../mol-plugin-state/manager/structure/component';
export { PluginState };
@@ -64,6 +65,9 @@ class PluginState extends PluginComponent {
canvas3d: p.canvas3d ? { props: this.plugin.canvas3d?.props } : void 0,
interactivity: p.interactivity ? { props: this.plugin.managers.interactivity.props } : void 0,
structureFocus: this.plugin.managers.structure.focus.getSnapshot(),
structureComponentManager: p.componentManager ? {
options: this.plugin.managers.structure.component.state.options
} : void 0,
durationInMs: p?.durationInMs
};
}
@@ -71,6 +75,8 @@ class PluginState extends PluginComponent {
async setSnapshot(snapshot: PluginState.Snapshot) {
await this.animation.stop();
// this needs to go 1st since these changes are already baked into the behavior and data state
if (snapshot.structureComponentManager?.options) await this.plugin.managers.structure.component.setOptions(snapshot.structureComponentManager?.options);
if (snapshot.behaviour) await this.plugin.runTask(this.behaviors.setSnapshot(snapshot.behaviour));
if (snapshot.data) await this.plugin.runTask(this.data.setSnapshot(snapshot.data));
if (snapshot.canvas3d?.props) {
@@ -139,6 +145,7 @@ namespace PluginState {
durationInMs: PD.Numeric(1500, { min: 100, max: 15000, step: 100 }, { label: 'Duration in ms' }),
data: PD.Boolean(true),
behavior: PD.Boolean(false),
componentManager: PD.Boolean(true),
animation: PD.Boolean(true),
startAnimation: PD.Boolean(false),
canvas3d: PD.Boolean(true),
@@ -172,6 +179,9 @@ namespace PluginState {
props?: InteractivityManager.Props
},
structureFocus?: StructureFocusSnapshot,
structureComponentManager?: {
options?: StructureComponentManager.Options
},
durationInMs?: number
}

View File

@@ -25,6 +25,7 @@ import { CustomProperty } from '../mol-model-props/common/custom-property';
import { Clipping } from '../mol-theme/clipping';
import { SetUtils } from '../mol-util/set';
import { cantorPairing } from '../mol-data/util';
import { Substance } from '../mol-theme/substance';
export type RepresentationProps = { [k: string]: any }
@@ -186,6 +187,8 @@ namespace Representation {
overpaint: Overpaint
/** Per group transparency applied to the representation's renderobjects */
transparency: Transparency
/** Per group material applied to the representation's renderobjects */
substance: Substance
/** Bit mask of per group clipping applied to the representation's renderobjects */
clipping: Clipping
/** Controls if the representation's renderobjects are synced automatically with GPU or not */
@@ -196,7 +199,7 @@ namespace Representation {
markerActions: MarkerActions
}
export function createState(): State {
return { visible: true, alphaFactor: 1, pickable: true, colorOnly: false, syncManually: false, transform: Mat4.identity(), overpaint: Overpaint.Empty, transparency: Transparency.Empty, clipping: Clipping.Empty, markerActions: MarkerActions.All };
return { visible: true, alphaFactor: 1, pickable: true, colorOnly: false, syncManually: false, transform: Mat4.identity(), overpaint: Overpaint.Empty, transparency: Transparency.Empty, substance: Substance.Empty, clipping: Clipping.Empty, markerActions: MarkerActions.All };
}
export function updateState(state: State, update: Partial<State>) {
if (update.visible !== undefined) state.visible = update.visible;
@@ -205,6 +208,7 @@ namespace Representation {
if (update.colorOnly !== undefined) state.colorOnly = update.colorOnly;
if (update.overpaint !== undefined) state.overpaint = update.overpaint;
if (update.transparency !== undefined) state.transparency = update.transparency;
if (update.substance !== undefined) state.substance = update.substance;
if (update.clipping !== undefined) state.clipping = update.clipping;
if (update.syncManually !== undefined) state.syncManually = update.syncManually;
if (update.transform !== undefined) Mat4.copy(state.transform, update.transform);
@@ -410,6 +414,9 @@ namespace Representation {
if (state.transparency !== undefined) {
// TODO
}
if (state.substance !== undefined) {
// TODO
}
if (state.transform !== undefined) Visual.setTransform(renderObject, state.transform);
Representation.updateState(currentState, state);

View File

@@ -216,6 +216,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
if (state.transparency !== undefined) {
Visual.setTransparency(_renderObject, state.transparency, lociApply, true);
}
if (state.substance !== undefined) {
Visual.setSubstance(_renderObject, state.substance, lociApply, true);
}
if (state.transform !== undefined) Visual.setTransform(_renderObject, state.transform);
}

View File

@@ -21,6 +21,7 @@ import { StructureParams } from './params';
import { Clipping } from '../../mol-theme/clipping';
import { Transparency } from '../../mol-theme/transparency';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Substance } from '../../mol-theme/substance';
export function ComplexRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, structure: Structure, props: PD.Values<P>, webgl?: WebGLContext) => ComplexVisual<P>): StructureRepresentation<P> {
let version = 0;
@@ -113,6 +114,11 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
const remappedTransparency = Transparency.remap(state.transparency, _structure);
visual.setTransparency(remappedTransparency, webgl);
}
if (state.substance !== undefined && visual) {
// Remap loci from equivalent structure to the current structure
const remappedSubstance = Substance.remap(state.substance, _structure);
visual.setSubstance(remappedSubstance, webgl);
}
if (state.clipping !== undefined && visual) {
// Remap loci from equivalent structure to the current structure
const remappedClipping = Clipping.remap(state.clipping, _structure);

View File

@@ -36,6 +36,7 @@ import { Clipping } from '../../mol-theme/clipping';
import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { isPromiseLike } from '../../mol-util/type-helpers';
import { Substance } from '../../mol-theme/substance';
export interface ComplexVisual<P extends StructureParams> extends Visual<Structure, P> { }
@@ -266,6 +267,10 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
const smoothing = { geometry, props: currentProps, webgl };
Visual.setTransparency(renderObject, transparency, lociApply, true, smoothing);
},
setSubstance(substance: Substance, webgl?: WebGLContext) {
const smoothing = { geometry, props: currentProps, webgl };
Visual.setSubstance(renderObject, substance, lociApply, true, smoothing);
},
setClipping(clipping: Clipping) {
Visual.setClipping(renderObject, clipping, lociApply, true);
},

View File

@@ -25,6 +25,7 @@ import { StructureParams } from './params';
import { Clipping } from '../../mol-theme/clipping';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { StructureGroup } from './visual/util/common';
import { Substance } from '../../mol-theme/substance';
export interface UnitsVisual<P extends StructureParams> extends Visual<StructureGroup, P> { }
@@ -218,13 +219,14 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
}
function setVisualState(visual: UnitsVisual<P>, group: Unit.SymmetryGroup, state: Partial<StructureRepresentationState>) {
const { visible, alphaFactor, pickable, overpaint, transparency, clipping, transform, unitTransforms } = state;
const { visible, alphaFactor, pickable, overpaint, transparency, substance, clipping, transform, unitTransforms } = state;
if (visible !== undefined) visual.setVisibility(visible);
if (alphaFactor !== undefined) visual.setAlphaFactor(alphaFactor);
if (pickable !== undefined) visual.setPickable(pickable);
if (overpaint !== undefined) visual.setOverpaint(overpaint, webgl);
if (transparency !== undefined) visual.setTransparency(transparency, webgl);
if (substance !== undefined) visual.setSubstance(substance, webgl);
if (clipping !== undefined) visual.setClipping(clipping);
if (transform !== undefined) visual.setTransform(transform);
if (unitTransforms !== undefined) {
@@ -238,7 +240,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
}
function setState(state: Partial<StructureRepresentationState>) {
const { visible, alphaFactor, pickable, overpaint, transparency, clipping, transform, unitTransforms, syncManually, markerActions } = state;
const { visible, alphaFactor, pickable, overpaint, transparency, substance, clipping, transform, unitTransforms, syncManually, markerActions } = state;
const newState: Partial<StructureRepresentationState> = {};
if (visible !== _state.visible) newState.visible = visible;
@@ -250,6 +252,9 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
if (transparency !== undefined && _structure) {
newState.transparency = Transparency.remap(transparency, _structure);
}
if (substance !== undefined && _structure) {
newState.substance = Substance.remap(substance, _structure);
}
if (clipping !== undefined && _structure) {
newState.clipping = Clipping.remap(clipping, _structure);
}

View File

@@ -40,6 +40,7 @@ import { StructureParams, StructureMeshParams, StructureSpheresParams, Structure
import { Clipping } from '../../mol-theme/clipping';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { isPromiseLike } from '../../mol-util/type-helpers';
import { Substance } from '../../mol-theme/substance';
export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
@@ -331,6 +332,10 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
const smoothing = { geometry, props: currentProps, webgl };
Visual.setTransparency(renderObject, transparency, lociApply, true, smoothing);
},
setSubstance(substance: Substance, webgl?: WebGLContext) {
const smoothing = { geometry, props: currentProps, webgl };
Visual.setSubstance(renderObject, substance, lociApply, true, smoothing);
},
setClipping(clipping: Clipping) {
Visual.setClipping(renderObject, clipping, lociApply, true);
},

View File

@@ -27,8 +27,10 @@ import { getMarkersAverage } from '../mol-geo/geometry/marker-data';
import { Texture } from '../mol-gl/webgl/texture';
import { Geometry } from '../mol-geo/geometry/geometry';
import { getColorSmoothingProps, hasColorSmoothingProp } from '../mol-geo/geometry/base';
import { applyMeshOverpaintSmoothing, applyMeshTransparencySmoothing } from '../mol-geo/geometry/mesh/color-smoothing';
import { applyTextureMeshOverpaintSmoothing, applyTextureMeshTransparencySmoothing } from '../mol-geo/geometry/texture-mesh/color-smoothing';
import { applyMeshOverpaintSmoothing, applyMeshSubstanceSmoothing, applyMeshTransparencySmoothing } from '../mol-geo/geometry/mesh/color-smoothing';
import { applyTextureMeshOverpaintSmoothing, applyTextureMeshSubstanceSmoothing, applyTextureMeshTransparencySmoothing } from '../mol-geo/geometry/texture-mesh/color-smoothing';
import { Substance } from '../mol-theme/substance';
import { applySubstanceMaterial, clearSubstance, createSubstance } from '../mol-geo/geometry/substance-data';
export interface VisualContext {
readonly runtime: RuntimeContext
@@ -51,6 +53,7 @@ interface Visual<D, P extends PD.Params> {
setTransform: (matrix?: Mat4, instanceMatrices?: Float32Array | null) => void
setOverpaint: (overpaint: Overpaint, webgl?: WebGLContext) => void
setTransparency: (transparency: Transparency, webgl?: WebGLContext) => void
setSubstance: (substance: Substance, webgl?: WebGLContext) => void
setClipping: (clipping: Clipping) => void
destroy: () => void
mustRecreate?: (data: D, props: PD.Values<P>, webgl?: WebGLContext) => boolean
@@ -143,6 +146,7 @@ namespace Visual {
resolution?: number
overpaintTexture?: Texture
transparencyTexture?: Texture
substanceTexture?: Texture
}
type SmoothingContext = {
@@ -248,6 +252,55 @@ namespace Visual {
}
}
export function setSubstance(renderObject: GraphicsRenderObject | undefined, substance: Substance, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
if (!renderObject) return;
const { tSubstance, dSubstanceType, uGroupCount, instanceCount } = renderObject.values;
const count = uGroupCount.ref.value * instanceCount.ref.value;
// ensure texture has right size
createSubstance(substance.layers.length ? count : 0, renderObject.values);
const { array } = tSubstance.ref.value;
// clear all if requested
if (clear) clearSubstance(array, 0, count);
for (let i = 0, il = substance.layers.length; i < il; ++i) {
const { loci, material, clear } = substance.layers[i];
const apply = (interval: Interval) => {
const start = Interval.start(interval);
const end = Interval.end(interval);
return clear
? clearSubstance(array, start, end)
: applySubstanceMaterial(array, start, end, material);
};
lociApply(loci, apply, false);
}
ValueCell.update(tSubstance, tSubstance.ref.value);
ValueCell.updateIfChanged(dSubstanceType, 'groupInstance');
if (substance.layers.length === 0) return;
if (smoothing && hasColorSmoothingProp(smoothing.props)) {
const { geometry, props, webgl } = smoothing;
if (geometry.kind === 'mesh') {
const { resolution, substanceTexture } = geometry.meta as SurfaceMeta;
const csp = getColorSmoothingProps(props.smoothColors, true, resolution);
if (csp) {
applyMeshSubstanceSmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, substanceTexture);
(geometry.meta as SurfaceMeta).substanceTexture = renderObject.values.tSubstanceGrid.ref.value;
}
} else if (webgl && geometry.kind === 'texture-mesh') {
const { resolution, substanceTexture } = geometry.meta as SurfaceMeta;
const csp = getColorSmoothingProps(props.smoothColors, true, resolution);
if (csp) {
applyTextureMeshSubstanceSmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, substanceTexture);
(geometry.meta as SurfaceMeta).substanceTexture = renderObject.values.tSubstanceGrid.ref.value;
}
}
}
}
export function setClipping(renderObject: GraphicsRenderObject | undefined, clipping: Clipping, lociApply: LociApply, clear: boolean) {
if (!renderObject) return;

View File

@@ -32,6 +32,7 @@ import { SizeValues } from '../../mol-gl/renderable/schema';
import { Clipping } from '../../mol-theme/clipping';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { isPromiseLike } from '../../mol-util/type-helpers';
import { Substance } from '../../mol-theme/substance';
export interface VolumeVisual<P extends VolumeParams> extends Visual<Volume, P> { }
@@ -211,6 +212,9 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
setTransparency(transparency: Transparency) {
return Visual.setTransparency(renderObject, transparency, lociApply, true);
},
setSubstance(substance: Substance) {
return Visual.setSubstance(renderObject, substance, lociApply, true);
},
setClipping(clipping: Clipping) {
return Visual.setClipping(renderObject, clipping, lociApply, true);
},

View File

@@ -68,7 +68,7 @@ namespace Overpaint {
const layers: Overpaint.Layer[] = [];
map.forEach((loci, colorOrClear) => {
const clear = colorOrClear === -1;
const color = colorOrClear === -1 ? Color(0) : colorOrClear;
const color = clear ? Color(0) : colorOrClear;
layers.push({ loci, color, clear });
});
return { layers };

135
src/mol-theme/substance.ts Normal file
View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Loci } from '../mol-model/loci';
import { Structure, StructureElement } from '../mol-model/structure';
import { Script } from '../mol-script/script';
import { Material } from '../mol-util/material';
import { shallowEqual } from '../mol-util/object';
export { Substance };
type Substance = { readonly layers: ReadonlyArray<Substance.Layer> }
function Substance(layers: ReadonlyArray<Substance.Layer>): Substance {
return { layers };
}
namespace Substance {
export type Layer = { readonly loci: StructureElement.Loci, readonly material: Material, readonly clear: boolean }
export const Empty: Substance = { layers: [] };
export function areEqual(sA: Substance, sB: Substance) {
if (sA.layers.length === 0 && sB.layers.length === 0) return true;
if (sA.layers.length !== sB.layers.length) return false;
for (let i = 0, il = sA.layers.length; i < il; ++i) {
if (sA.layers[i].clear !== sB.layers[i].clear) return false;
if (!shallowEqual(sA.layers[i].material, sB.layers[i].material)) return false;
if (!Loci.areEqual(sA.layers[i].loci, sB.layers[i].loci)) return false;
}
return true;
}
export function isEmpty(overpaint: Substance) {
return overpaint.layers.length === 0;
}
export function remap(substance: Substance, structure: Structure) {
const layers: Substance.Layer[] = [];
for (const layer of substance.layers) {
let { loci, material, clear } = layer;
loci = StructureElement.Loci.remap(loci, structure);
if (!StructureElement.Loci.isEmpty(loci)) {
layers.push({ loci, material, clear });
}
}
return { layers };
}
export function merge(substance: Substance): Substance {
if (isEmpty(substance)) return substance;
const { structure } = substance.layers[0].loci;
let clearLoci: StructureElement.Loci | undefined = void 0;
const map = new Map<Material, StructureElement.Loci>();
let shadowed = StructureElement.Loci.none(structure);
for (let i = 0, il = substance.layers.length; i < il; ++i) {
let { loci, material, clear } = substance.layers[il - i - 1]; // process from end
loci = StructureElement.Loci.subtract(loci, shadowed);
shadowed = StructureElement.Loci.union(loci, shadowed);
if (!StructureElement.Loci.isEmpty(loci)) {
if (clear) {
clearLoci = clearLoci
? StructureElement.Loci.union(loci, clearLoci)
: loci;
} else {
if (map.has(material)) {
loci = StructureElement.Loci.union(loci, map.get(material)!);
}
map.set(material, loci);
}
}
}
const layers: Substance.Layer[] = [];
if (clearLoci) {
layers.push({ loci: clearLoci, material: Material(), clear: true });
}
map.forEach((loci, material) => {
layers.push({ loci, material, clear: false });
});
return { layers };
}
export function filter(substance: Substance, filter: Structure): Substance {
if (isEmpty(substance)) return substance;
const { structure } = substance.layers[0].loci;
const layers: Substance.Layer[] = [];
for (const layer of substance.layers) {
let { loci, material, clear } = layer;
// filter by first map to the `filter` structure and
// then map back to the original structure of the substance loci
const filtered = StructureElement.Loci.remap(loci, filter);
loci = StructureElement.Loci.remap(filtered, structure);
if (!StructureElement.Loci.isEmpty(loci)) {
layers.push({ loci, material, clear });
}
}
return { layers };
}
export type ScriptLayer = { script: Script, material: Material, clear: boolean }
export function ofScript(scriptLayers: ScriptLayer[], structure: Structure): Substance {
const layers: Substance.Layer[] = [];
for (let i = 0, il = scriptLayers.length; i < il; ++i) {
const { script, material, clear } = scriptLayers[i];
const loci = Script.toLoci(script, structure);
if (!StructureElement.Loci.isEmpty(loci)) {
layers.push({ loci, material, clear });
}
}
return { layers };
}
export type BundleLayer = { bundle: StructureElement.Bundle, material: Material, clear: boolean }
export function ofBundle(bundleLayers: BundleLayer[], structure: Structure): Substance {
const layers: Substance.Layer[] = [];
for (let i = 0, il = bundleLayers.length; i < il; ++i) {
const { bundle, material, clear } = bundleLayers[i];
const loci = StructureElement.Bundle.toLoci(bundle, structure.root);
layers.push({ loci, material, clear });
}
return { layers };
}
export function toBundle(overpaint: Substance) {
const layers: BundleLayer[] = [];
for (let i = 0, il = overpaint.layers.length; i < il; ++i) {
const { loci, material, clear } = overpaint.layers[i];
const bundle = StructureElement.Bundle.fromLoci(loci);
layers.push({ bundle, material, clear });
}
return { layers };
}
}

48
src/mol-util/material.ts Normal file
View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { NumberArray } from './type-helpers';
import { ParamDefinition as PD } from './param-definition';
export interface Material {
/** Normalized to [0, 1] range */
metalness: number,
/** Normalized to [0, 1] range */
roughness: number
}
export function Material(values?: Partial<Material>) {
return { ...Material.Zero, ...values };
}
export namespace Material {
export const Zero: Material = { metalness: 0, roughness: 0 };
export function toArray(material: Material, array: NumberArray, offset: number) {
array[offset] = material.metalness * 255;
array[offset + 1] = material.roughness * 255;
return array;
}
export function toString({ metalness, roughness }: Material) {
return `M ${metalness.toFixed(2)} | R ${roughness.toFixed(2)}`;
}
export function getParam(info?: { isExpanded?: boolean, isFlat?: boolean }) {
return PD.Group({
metalness: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
roughness: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
}, {
...info,
presets: [
[{ metalness: 0, roughness: 1 }, 'Matte'],
[{ metalness: 0, roughness: 0.2 }, 'Plastic'],
[{ metalness: 0, roughness: 0.6 }, 'Glossy'],
[{ metalness: 1.0, roughness: 0.6 }, 'Metallic'],
]
});
}
}

View File

@@ -68,4 +68,8 @@ export function getPrecision(v: number) {
export function toPrecision(v: number, precision: number) {
return parseFloat(v.toPrecision(precision));
}
export function toFixed(v: number, fractionDigits: number) {
return parseFloat(v.toFixed(fractionDigits));
}

View File

@@ -225,12 +225,14 @@ export namespace ParamDefinition {
export interface Group<T> extends Base<T> {
type: 'group',
params: Params,
presets?: Select<T>['options'],
isExpanded?: boolean,
isFlat?: boolean,
pivot?: keyof T
}
export function Group<T>(params: For<T>, info?: Info & { isExpanded?: boolean, isFlat?: boolean, customDefault?: any, pivot?: keyof T }): Group<Normalize<T>> {
export function Group<T>(params: For<T>, info?: Info & { isExpanded?: boolean, isFlat?: boolean, customDefault?: any, pivot?: keyof T, presets?: Select<T>['options'] }): Group<Normalize<T>> {
const ret = setInfo<Group<Normalize<T>>>({ type: 'group', defaultValue: info?.customDefault || getDefaultValues(params as any as Params) as any, params: params as any as Params }, info);
if (info?.presets) ret.presets = info.presets;
if (info?.isExpanded) ret.isExpanded = info.isExpanded;
if (info?.isFlat) ret.isFlat = info.isFlat;
if (info?.pivot) ret.pivot = info.pivot as any;