From 7eed7de093eb90f45529d416c7a092ea5e71e854 Mon Sep 17 00:00:00 2001 From: Faraz Shaikh Date: Sun, 1 May 2022 15:54:04 +0530 Subject: [PATCH] Refactor; Add loaders; Start debugger refactor --- .../public/Monkey w_ freckles.json | 150 +++++++++++++ examples/example-configurator/public/UV.jpg | Bin 0 -> 18819 bytes examples/example-configurator/src/Monkey.tsx | 32 +-- examples/example-configurator/yarn.lock | 57 ++--- examples/mesh-gradients/yarn.lock | 40 ++-- package.json | 5 + rollup.config.js | 16 +- scripts/link.sh | 10 + src/CSM/index.tsx | 13 -- src/CSM/keywords.ts | 8 - src/CSM/patchMaps.ts | 42 ---- src/CSM/types.ts | 31 --- src/CSM/vanilla.ts | 160 -------------- src/core/LayerMaterial.ts | 172 +++++++++++++++ src/core/Loader.ts | 97 +++++++++ src/core/debugger/index.tsx | 32 +++ src/core/debugger/useAttach.tsx | 34 +++ src/core/debugger/useExports.ts | 32 +++ src/{ => core}/index.tsx | 25 +-- src/core/{ => layers}/Abstract.ts | 66 +++--- src/core/{ => layers}/Color.ts | 2 +- src/core/{ => layers}/Depth.ts | 28 +-- src/core/{ => layers}/Displace.ts | 2 +- src/core/{ => layers}/Fresnel.ts | 2 +- src/core/{ => layers}/Gradient.ts | 2 +- src/core/{ => layers}/Matcap.ts | 16 +- src/core/{ => layers}/Noise.ts | 71 +++--- src/core/{ => layers}/Normal.ts | 2 +- src/core/{ => layers}/Shader.ts | 3 +- src/core/{ => layers}/Texture.ts | 4 +- src/core/layers/index.ts | 23 ++ src/core/useLoader.tsx | 33 +++ src/debug.ts | 1 + src/debug.tsx | 206 ------------------ src/index.ts | 2 + src/types.ts | 43 +++- src/utils/ExportUtils.ts | 136 +++++++++--- src/utils/Functions.ts | 12 +- src/vanilla.ts | 176 +-------------- tsconfig.json | 3 +- yarn.lock | 12 +- 41 files changed, 911 insertions(+), 890 deletions(-) create mode 100644 examples/example-configurator/public/Monkey w_ freckles.json create mode 100644 examples/example-configurator/public/UV.jpg delete mode 100644 src/CSM/index.tsx delete mode 100644 src/CSM/keywords.ts delete mode 100644 src/CSM/patchMaps.ts delete mode 100644 src/CSM/types.ts delete mode 100644 src/CSM/vanilla.ts create mode 100644 src/core/LayerMaterial.ts create mode 100644 src/core/Loader.ts create mode 100644 src/core/debugger/index.tsx create mode 100644 src/core/debugger/useAttach.tsx create mode 100644 src/core/debugger/useExports.ts rename src/{ => core}/index.tsx (90%) rename src/core/{ => layers}/Abstract.ts (90%) rename src/core/{ => layers}/Color.ts (90%) rename src/core/{ => layers}/Depth.ts (74%) rename src/core/{ => layers}/Displace.ts (98%) rename src/core/{ => layers}/Fresnel.ts (96%) rename src/core/{ => layers}/Gradient.ts (96%) rename src/core/{ => layers}/Matcap.ts (84%) rename src/core/{ => layers}/Noise.ts (62%) rename src/core/{ => layers}/Normal.ts (94%) rename src/core/{ => layers}/Shader.ts (93%) rename src/core/{ => layers}/Texture.ts (88%) create mode 100644 src/core/layers/index.ts create mode 100644 src/core/useLoader.tsx create mode 100644 src/debug.ts delete mode 100644 src/debug.tsx create mode 100644 src/index.ts diff --git a/examples/example-configurator/public/Monkey w_ freckles.json b/examples/example-configurator/public/Monkey w_ freckles.json new file mode 100644 index 0000000..dde45b9 --- /dev/null +++ b/examples/example-configurator/public/Monkey w_ freckles.json @@ -0,0 +1,150 @@ +{ + "metadata": { "version": 1, "type": "mat" }, + "base": { + "constructor": "LayerMaterial", + "currents": { + "metadata": { "version": 4.5, "type": "Material", "generator": "Material.toJSON" }, + "uuid": "b5b5d6dc57b444d92fc9ccb623668bb71de8a11a", + "type": "MeshBasicMaterial", + "name": "Monkey w/ freckles", + "color": 16711422, + "reflectivity": 1, + "refractionRatio": 0.98, + "transparent": true, + "depthFunc": 3, + "depthTest": true, + "depthWrite": true, + "colorWrite": true, + "stencilWrite": false, + "stencilWriteMask": 255, + "stencilFunc": 519, + "stencilRef": 0, + "stencilFuncMask": 255, + "stencilFail": 7680, + "stencilZFail": 7680, + "stencilZPass": 7680, + "lighting": "basic" + } + }, + "layers": [ + { + "constructor": "Abstract", + "fragment": " \n uniform float u_alpha;\n uniform float u_near;\n uniform float u_far;\n uniform float u_isVector;\n uniform vec3 u_origin;\n uniform vec3 u_colorA;\n uniform vec3 u_colorB;\n\n varying vec3 v_worldPosition;\n varying vec3 v_position;\n\n void main() {\n float f_dist = lamina_mapping_template;\n float f_depth = (f_dist - u_near) / (u_far - u_near);\n\t\t\tvec3 f_depthColor = mix(u_colorB, u_colorA, 1.0 - clamp(f_depth, 0., 1.));\n \n \n return vec4(f_depthColor, u_alpha);\n }\n ", + "vertex": "\n varying vec3 v_worldPosition;\n varying vec3 v_position;\n\n void main() {\n v_worldPosition = (vec4(position, 1.0) * modelMatrix).xyz;\n v_position = position;\n }\n ", + "uniforms": { "near": 2, "far": 10, "origin": [0, 0, 3], "colorA": "white", "colorB": "black", "alpha": 1 }, + "nonUniforms": { "mode": "normal", "visible": true, "name": "Depth$1", "mapping": "vector" }, + "currents": { + "near": -0.07, + "far": 5, + "origin": [0, 0, 3], + "colorA": "#fe4eb8", + "colorB": "#24dbf8", + "alpha": 1, + "mode": "normal", + "visible": true, + "name": "Depth", + "mapping": "vector" + }, + "functions": { + "onShaderParse": "self => {\n function getMapping(uuid, type) {\n switch (type) {\n default:\n case 'vector':\n return `length(v_${uuid}_worldPosition - u_${uuid}_origin)`;\n\n case 'world':\n return `length(v_${uuid}_position - vec3(0.))`;\n\n case 'camera':\n return `length(v_${uuid}_worldPosition - cameraPosition)`;\n }\n }\n\n self.schema.push({\n value: self.mapping,\n label: 'mapping',\n options: ['vector', 'world', 'camera']\n });\n const mapping = getMapping(self.uuid, self.mapping);\n self.fragmentShader = self.fragmentShader.replace('lamina_mapping_template', mapping);\n }" + } + }, + { + "constructor": "Abstract", + "fragment": " \n uniform float u_alpha;\n uniform float u_near;\n uniform float u_far;\n uniform float u_isVector;\n uniform vec3 u_origin;\n uniform vec3 u_colorA;\n uniform vec3 u_colorB;\n\n varying vec3 v_worldPosition;\n varying vec3 v_position;\n\n void main() {\n float f_dist = lamina_mapping_template;\n float f_depth = (f_dist - u_near) / (u_far - u_near);\n\t\t\tvec3 f_depthColor = mix(u_colorB, u_colorA, 1.0 - clamp(f_depth, 0., 1.));\n \n \n return vec4(f_depthColor, u_alpha);\n }\n ", + "vertex": "\n varying vec3 v_worldPosition;\n varying vec3 v_position;\n\n void main() {\n v_worldPosition = (vec4(position, 1.0) * modelMatrix).xyz;\n v_position = position;\n }\n ", + "uniforms": { "near": 2, "far": 10, "origin": [0, 0, -1.37], "colorA": "white", "colorB": "black", "alpha": 1 }, + "nonUniforms": { "mode": "normal", "visible": true, "name": "Depth$1", "mapping": "vector" }, + "currents": { + "near": 1, + "far": 3, + "origin": [0, 0, -1.37], + "colorA": "#fe7800", + "colorB": "#000000", + "alpha": 1, + "mode": "screen", + "visible": true, + "name": "Depth", + "mapping": "vector" + }, + "functions": { + "onShaderParse": "self => {\n function getMapping(uuid, type) {\n switch (type) {\n default:\n case 'vector':\n return `length(v_${uuid}_worldPosition - u_${uuid}_origin)`;\n\n case 'world':\n return `length(v_${uuid}_position - vec3(0.))`;\n\n case 'camera':\n return `length(v_${uuid}_worldPosition - cameraPosition)`;\n }\n }\n\n self.schema.push({\n value: self.mapping,\n label: 'mapping',\n options: ['vector', 'world', 'camera']\n });\n const mapping = getMapping(self.uuid, self.mapping);\n self.fragmentShader = self.fragmentShader.replace('lamina_mapping_template', mapping);\n }" + } + }, + { + "constructor": "Abstract", + "fragment": " \n uniform vec3 u_color;\n uniform float u_alpha;\n uniform float u_bias;\n uniform float u_intensity;\n uniform float u_power;\n uniform float u_factor;\n\n varying vec3 v_worldPosition;\n varying vec3 v_worldNormal;\n\n void main() {\n float f_a = (u_factor + dot(v_worldPosition, v_worldNormal));\n float f_fresnel = u_bias + u_intensity * pow(abs(f_a), u_power);\n\n f_fresnel = clamp(f_fresnel, 0.0, 1.0);\n return vec4(f_fresnel * u_color, u_alpha);\n }\n ", + "vertex": "\n varying vec3 v_worldPosition;\n varying vec3 v_worldNormal;\n\n void main() {\n v_worldPosition = vec3(-viewMatrix[0][2], -viewMatrix[1][2], -viewMatrix[2][2]);\n v_worldNormal = normalize( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );\n \n }\n ", + "uniforms": { "color": "white", "alpha": 1, "bias": 0, "intensity": 1, "power": 2, "factor": 1 }, + "nonUniforms": { "mode": "normal", "visible": true, "name": "Fresnel$1" }, + "currents": { + "color": "#fefefe", + "alpha": 1, + "bias": 0, + "intensity": 1, + "power": 1.91, + "factor": 1, + "mode": "softlight", + "visible": true, + "name": "Fresnel" + }, + "functions": {} + }, + { + "constructor": "Abstract", + "fragment": " \n uniform vec3 u_colorA;\n uniform vec3 u_colorB;\n uniform vec3 u_colorC;\n uniform vec3 u_colorD;\n uniform vec3 u_offset;\n\n uniform float u_alpha;\n uniform float u_scale;\n\n varying vec3 v_position;\n\n\n void main() {\n float f_n = lamina_noise_template((v_position + u_offset) * u_scale);\n\n float f_step1 = 0.;\n float f_step2 = 0.2;\n float f_step3 = 0.6;\n float f_step4 = 1.;\n\n vec3 f_color = mix(u_colorA, u_colorB, smoothstep(f_step1, f_step2, f_n));\n f_color = mix(f_color, u_colorC, smoothstep(f_step2, f_step3, f_n));\n f_color = mix(f_color, u_colorD, smoothstep(f_step3, f_step4, f_n));\n\n return vec4(f_color, u_alpha);\n }\n ", + "vertex": "\n varying vec3 v_position;\n\n void main() {\n v_position = lamina_mapping_template;\n }\n ", + "uniforms": { + "colorA": "#666666", + "colorB": "#666666", + "colorC": "#FFFFFF", + "colorD": "#FFFFFF", + "alpha": 1, + "scale": 1, + "offset": [0, 0, 0] + }, + "nonUniforms": { "mode": "normal", "visible": true, "name": "Noise$1", "type": "perlin", "mapping": "local" }, + "currents": { + "colorA": "#3bfed0", + "colorB": "#4e4e4e", + "colorC": "#000000", + "colorD": "#000000", + "alpha": 1, + "scale": 50, + "offset": [0, 0, 0], + "mode": "lighten", + "visible": true, + "name": "noise", + "type": "perlin", + "mapping": "local" + }, + "functions": { + "onShaderParse": "self => {\n function getNoiseFunction(type) {\n switch (type) {\n default:\n case 'perlin':\n return `lamina_noise_perlin`;\n\n case 'simplex':\n return `lamina_noise_simplex`;\n\n case 'cell':\n return `lamina_noise_worley`;\n\n case 'white':\n return `lamina_noise_white`;\n\n case 'curl':\n return `lamina_noise_swirl`;\n }\n }\n\n function getMapping(type) {\n switch (type) {\n default:\n case 'local':\n return `position`;\n\n case 'world':\n return `(modelMatrix * vec4(position,1.0)).xyz`;\n\n case 'uv':\n return `vec3(uv, 0.)`;\n }\n }\n\n self.schema.push({\n value: self.type,\n label: 'type',\n options: ['perlin', 'simplex', 'cell', 'curl', 'white']\n });\n self.schema.push({\n value: self.mapping,\n label: 'mapping',\n options: ['local', 'world', 'uv']\n });\n const noiseFunc = getNoiseFunction(self.type);\n const mapping = getMapping(self.mapping);\n self.vertexShader = self.vertexShader.replace('lamina_mapping_template', mapping);\n self.fragmentShader = self.fragmentShader.replace('lamina_noise_template', noiseFunc);\n }" + } + }, + { + "constructor": "Abstract", + "fragment": " \n\t\tuniform sampler2D u_map; \n\t\tuniform float u_alpha; \n\t\tvarying vec2 v_uv;\n\n void main() {\n\t\t\tvec3 f_color = texture2D(u_map, v_uv).rgb;\n return vec4(f_color, u_alpha);\n }\n ", + "vertex": "\n varying vec2 v_uv;\n \n void main() {\n v_uv = uv;\n }\n ", + "uniforms": { "alpha": 1 }, + "nonUniforms": { "mode": "normal", "visible": true, "name": "Texture$1" }, + "currents": { "alpha": 1, "mode": "normal", "visible": true, "name": "Texture$1" }, + "functions": {} + }, + { + "constructor": "Texture", + "fragment": " \n\t\tuniform sampler2D u_map; \n\t\tuniform float u_alpha; \n\t\tvarying vec2 v_uv;\n\n void main() {\n\t\t\tvec3 f_color = texture2D(u_map, v_uv).rgb;\n return vec4(f_color, u_alpha);\n }\n ", + "vertex": "\n varying vec2 v_uv;\n \n void main() {\n v_uv = uv;\n }\n ", + "uniforms": { "alpha": 1, "map": {} }, + "nonUniforms": { "mode": "normal", "visible": true, "name": "Texture$1" }, + "currents": { + "alpha": 1, + "map": "data:image/jpeg;base64,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", + "mode": "normal", + "visible": true, + "name": "Texture$1" + }, + "functions": {} + } + ] +} diff --git a/examples/example-configurator/public/UV.jpg b/examples/example-configurator/public/UV.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e05927dc758f246de0d01f76ac468a39fc06e73c GIT binary patch literal 18819 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 fMH(null!) const transformControls = React.useRef(null!) @@ -24,21 +24,7 @@ export default function Monkey() { }) const [layers, setLayers] = useState([]) - const [materialProps, setMaterialProps] = useState<{ [key: string]: any }>({}) - - useEffect(() => { - ;(async () => { - const json = await (await fetch('/initialMaterial.json')).json() - const l = json.layers.map((layer: SerializedLayer) => { - // @ts-ignore - const Component = LAYERS[layer.constructor] - return - }) - - setMaterialProps(json.properties) - setLayers(l) - })() - }, []) + const InitialMaterial = LAYERS.useLamina('/Monkey w_ freckles.json') useControls('Layers', { Type: { @@ -64,13 +50,15 @@ export default function Monkey() { }), }) + const tex = useTexture('/UV.jpg') + return ( <> - - {...layers} + + {layers} diff --git a/examples/example-configurator/yarn.lock b/examples/example-configurator/yarn.lock index 4dfa110..fbbfe99 100644 --- a/examples/example-configurator/yarn.lock +++ b/examples/example-configurator/yarn.lock @@ -1519,9 +1519,9 @@ destroy@1.2.0: integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== detect-gpu@^4.0.19: - version "4.0.19" - resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-4.0.19.tgz#e89abefb7de298405186e8a0b6f7fb6a156ad66e" - integrity sha512-ZOPhyEkOBc1WqCfrdB6ymLW5MZqxge2QPvqPt/MhWev+vBrAwxH452dkghRfdNYvCikmjUnsZsHQtBpmXH4uJA== + version "4.0.20" + resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-4.0.20.tgz#e0eb95bfc6f6e96303c8140dc7b206be43dd5a0a" + integrity sha512-pHQnqxYn4TC9Xw6b4DZCJxn+X+YGcPp4c+Eq6u6OhjbrniZeqiQiBFN3Sz2JuNanx4IzyDvm2FTdj3yEmrAjYQ== dependencies: webgl-constants "^1.1.1" @@ -1603,9 +1603,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.4.118: - version "1.4.127" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.127.tgz#4ef19d5d920abe2676d938f4170729b44f7f423a" - integrity sha512-nhD6S8nKI0O2MueC6blNOEZio+/PWppE/pevnf3LOlQA/fKPCrDp2Ao4wx4LFwmIkJpVdFdn2763YWLy9ENIZg== + version "1.4.129" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.129.tgz#c675793885721beefff99da50f57c6525c2cd238" + integrity sha512-GgtN6bsDtHdtXJtlMYZWGB/uOyjZWjmRDumXTas7dGBaB9zUyCjzHet1DY2KhyHN8R0GLbzZWqm4efeddqqyRQ== encodeurl@~1.0.2: version "1.0.2" @@ -1713,9 +1713,9 @@ execa@^5.0.0: strip-final-newline "^2.0.0" express@^4.17.3: - version "4.18.0" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.0.tgz#7a426773325d0dd5406395220614c0db10b6e8e2" - integrity sha512-EJEXxiTQJS3lIPrU1AE2vRuT7X7E+0KBbpm5GSoK524yl0K8X+er8zS2P14E64eqsVNoWbMCT7MpmQ+ErAhgRg== + version "4.18.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" + integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" @@ -1837,9 +1837,9 @@ for-in@^1.0.2: integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= fork-ts-checker-webpack-plugin@^7.2.1: - version "7.2.8" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.8.tgz#afe403ed92bb49a3456549ee518e28b35d213452" - integrity sha512-kvOM0w3Hi66o3qNLWNN9mWcMXwZNjuQ0+LMloxAM+QL5ZRGV6sLZnFyM86qKLNWklEG5XofcYWSAVosjtNg+vg== + version "7.2.9" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.9.tgz#4677c1bde10b492df6dc56ebaa538c14f0370782" + integrity sha512-tiK7gAyND3pK7CFXgKXjzBQ4w2SuxPCHDsIUSN7qJD6vUNKt2LHAb+4nxm5GhxI5ADK8aFYIWQv3knC24k4sRg== dependencies: "@babel/code-frame" "^7.16.7" chalk "^4.1.2" @@ -2389,20 +2389,7 @@ ktx-parse@^0.2.1: resolved "https://registry.yarnpkg.com/ktx-parse/-/ktx-parse-0.2.2.tgz#b037b66044855215b332cb73104590af49e47791" integrity sha512-cFBc1jnGG2WlUf52NbDUXK2obJ+Mo9WUkBRvr6tP6CKxRMvZwDDFNV3JAS4cewETp5KyexByfWm9sm+O8AffiQ== -lamina@^1.1.16, lamina@^1.1.17: - version "1.1.17" - resolved "https://registry.yarnpkg.com/lamina/-/lamina-1.1.17.tgz#9f2141debac6b1d9e4e2b23bb08f205a8166ccd1" - integrity sha512-VwsS791VZ/IPH+DQ7nIqJZz9Orcwbb6KmMZeKSbKeQleKYDMVAJLvAf7yXb8D0N3hYo67anc+HkoeacGgh3e6w== - dependencies: - glsl-token-descope "^1.0.2" - glsl-token-functions "^1.0.1" - glsl-token-string "^1.0.1" - glsl-tokenizer "^2.1.5" - lamina "^1.1.16" - leva "^0.9.20" - three-custom-shader-material "^3.3.3" - -lamina@^1.1.18: +lamina@^1.1.17, lamina@^1.1.18: version "1.1.18" resolved "https://registry.yarnpkg.com/lamina/-/lamina-1.1.18.tgz#0de06c2698eba89708426f0f22e9a77a066f1bfa" integrity sha512-v9xWmIqTfRSpp2VFg+Y6IpXvRtRsVlLxvdq1iLpl2yxukMvumFdp8M79QNciVgHh7UbaK3C9sOF6bUTsPwNipA== @@ -2628,7 +2615,7 @@ multicast-dns@^7.2.4: dns-packet "^5.2.2" thunky "^1.0.2" -nanoid@*, nanoid@^3.3.1, nanoid@^3.3.3: +nanoid@*, nanoid@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== @@ -2916,11 +2903,11 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.4.7: - version "8.4.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" - integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== + version "8.4.13" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575" + integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA== dependencies: - nanoid "^3.3.1" + nanoid "^3.3.3" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -3554,7 +3541,7 @@ terser@^5.10.0, terser@^5.7.2: source-map "~0.8.0-beta.0" source-map-support "~0.5.20" -three-custom-shader-material@^3.3.3, three-custom-shader-material@^3.3.4: +three-custom-shader-material@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/three-custom-shader-material/-/three-custom-shader-material-3.3.4.tgz#e96e50d6d29b9647bcaa089deee65e4942b46521" integrity sha512-WjdFw/eobZfr5ezzzpFWF7Jn/YHYOKshDDboF1rplfCePSKUegwlZyfkC3/Et3m7RCNb7QHmYqb542ad2+m2hw== @@ -3651,9 +3638,9 @@ troika-worker-utils@^0.46.0: integrity sha512-bzOx5f2ZBxkFhXtIvDJlLn2AI3bzCkGVbCndl/2dL5QZrwHEKl45OEIilCxYQQWJG1rEbOD9O80tMjoYjw19OA== ts-loader@^9.2.8: - version "9.2.9" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.9.tgz#0653e07fa1b4f225d0ca57a84fddbfd43d930f9e" - integrity sha512-b0+vUY2/enb0qYtDQuNlDnJ9900NTiPiJcDJ6sY7ax1CCCwXfYIqPOMm/BwW7jsF1km+Oz8W9s31HLuD+FLIMg== + version "9.3.0" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.3.0.tgz#980f4dbfb60e517179e15e10ed98e454b132159f" + integrity sha512-2kLLAdAD+FCKijvGKi9sS0OzoqxLCF3CxHpok7rVgCZ5UldRzH0TkbwG9XECKjBzHsAewntC5oDaI/FwKzEUog== dependencies: chalk "^4.1.0" enhanced-resolve "^5.0.0" diff --git a/examples/mesh-gradients/yarn.lock b/examples/mesh-gradients/yarn.lock index 4c6c119..d2a492f 100644 --- a/examples/mesh-gradients/yarn.lock +++ b/examples/mesh-gradients/yarn.lock @@ -3790,9 +3790,9 @@ destroy@1.2.0: integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== detect-gpu@^4.0.19: - version "4.0.19" - resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-4.0.19.tgz#e89abefb7de298405186e8a0b6f7fb6a156ad66e" - integrity sha512-ZOPhyEkOBc1WqCfrdB6ymLW5MZqxge2QPvqPt/MhWev+vBrAwxH452dkghRfdNYvCikmjUnsZsHQtBpmXH4uJA== + version "4.0.20" + resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-4.0.20.tgz#e0eb95bfc6f6e96303c8140dc7b206be43dd5a0a" + integrity sha512-pHQnqxYn4TC9Xw6b4DZCJxn+X+YGcPp4c+Eq6u6OhjbrniZeqiQiBFN3Sz2JuNanx4IzyDvm2FTdj3yEmrAjYQ== dependencies: webgl-constants "^1.1.1" @@ -3977,9 +3977,9 @@ ejs@^3.1.6: jake "^10.8.5" electron-to-chromium@^1.4.118: - version "1.4.127" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.127.tgz#4ef19d5d920abe2676d938f4170729b44f7f423a" - integrity sha512-nhD6S8nKI0O2MueC6blNOEZio+/PWppE/pevnf3LOlQA/fKPCrDp2Ao4wx4LFwmIkJpVdFdn2763YWLy9ENIZg== + version "1.4.129" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.129.tgz#c675793885721beefff99da50f57c6525c2cd238" + integrity sha512-GgtN6bsDtHdtXJtlMYZWGB/uOyjZWjmRDumXTas7dGBaB9zUyCjzHet1DY2KhyHN8R0GLbzZWqm4efeddqqyRQ== emittery@^0.10.2: version "0.10.2" @@ -4421,9 +4421,9 @@ expect@^27.5.1: jest-message-util "^27.5.1" express@^4.17.3: - version "4.18.0" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.0.tgz#7a426773325d0dd5406395220614c0db10b6e8e2" - integrity sha512-EJEXxiTQJS3lIPrU1AE2vRuT7X7E+0KBbpm5GSoK524yl0K8X+er8zS2P14E64eqsVNoWbMCT7MpmQ+ErAhgRg== + version "4.18.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" + integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" @@ -6070,9 +6070,9 @@ jsonpointer@^5.0.0: integrity sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg== "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz#6ab1e52c71dfc0c0707008a91729a9491fe9f76c" - integrity sha512-HDAyJ4MNQBboGpUnHAVUNJs6X0lh058s6FuixsFGP7MgJYpD6Vasd6nzSG5iIfXu1zAYlHJ/zsOKNlrenTUBnw== + version "3.3.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz#e624f259143b9062c92b6413ff92a164c80d3ccb" + integrity sha512-XzO9luP6L0xkxwhIJMTJQpZo/eeN60K08jHdexfD569AGxeNug6UketeHXEhROoM8aR7EcUoOQmIhcJQjcuq8Q== dependencies: array-includes "^3.1.4" object.assign "^4.1.2" @@ -6470,7 +6470,7 @@ multicast-dns@^7.2.4: dns-packet "^5.2.2" thunky "^1.0.2" -nanoid@*, nanoid@^3.3.1, nanoid@^3.3.3: +nanoid@*, nanoid@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== @@ -7416,11 +7416,11 @@ postcss@^7.0.35: source-map "^0.6.1" postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.4, postcss@^8.4.7: - version "8.4.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" - integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== + version "8.4.13" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575" + integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA== dependencies: - nanoid "^3.3.1" + nanoid "^3.3.3" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -7974,9 +7974,9 @@ rollup-plugin-terser@^7.0.0: terser "^5.0.0" rollup@^2.43.1: - version "2.70.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.2.tgz#808d206a8851628a065097b7ba2053bd83ba0c0d" - integrity sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg== + version "2.71.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.71.1.tgz#82b259af7733dfd1224a8171013aaaad02971a22" + integrity sha512-lMZk3XfUBGjrrZQpvPSoXcZSfKcJ2Bgn+Z0L1MoW2V8Wh7BVM+LOBJTPo16yul2MwL59cXedzW1ruq3rCjSRgw== optionalDependencies: fsevents "~2.3.2" diff --git a/package.json b/package.json index d15c588..4a574f9 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,11 @@ "types": "./vanilla.d.ts", "require": "./vanilla.cjs.js", "import": "./vanilla.js" + }, + "./debug": { + "types": "./debug.d.ts", + "require": "./debug.cjs.js", + "import": "./debug.js" } }, "keywords": [ diff --git a/rollup.config.js b/rollup.config.js index e2a07a9..b6b6ddc 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -35,13 +35,13 @@ const getBabelOptions = ({ useESModules }) => ({ export default [ { - input: `./src/index.tsx`, + input: `./src/index.ts`, output: { file: `dist/index.js`, format: 'esm' }, external, plugins: [babel(getBabelOptions({ useESModules: true })), resolve({ extensions })], }, { - input: `./src/index.tsx`, + input: `./src/index.ts`, output: { file: `dist/index.cjs.js`, format: 'cjs' }, external, plugins: [babel(getBabelOptions({ useESModules: false })), resolve({ extensions })], @@ -58,4 +58,16 @@ export default [ external, plugins: [babel(getBabelOptions({ useESModules: false })), resolve({ extensions })], }, + { + input: `./src/debug.ts`, + output: { file: `dist/debug.js`, format: 'esm' }, + external, + plugins: [babel(getBabelOptions({ useESModules: true })), resolve({ extensions })], + }, + { + input: `./src/debug.ts`, + output: { file: `dist/debug.cjs.js`, format: 'cjs' }, + external, + plugins: [babel(getBabelOptions({ useESModules: false })), resolve({ extensions })], + }, ] diff --git a/scripts/link.sh b/scripts/link.sh index 3d63303..e7c5f9f 100755 --- a/scripts/link.sh +++ b/scripts/link.sh @@ -36,3 +36,13 @@ cd ../../../ yarn link three yarn link @react-three/fiber cd ../../ + + cd ./examples/example-configurator + rm -rf ./node_modeules ./yarn.lock + yarn + + yarn link lamina + yarn link react + yarn link three + yarn link @react-three/fiber + cd ../../ diff --git a/src/CSM/index.tsx b/src/CSM/index.tsx deleted file mode 100644 index c7755ff..0000000 --- a/src/CSM/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React, { forwardRef, useMemo } from 'react' -import CustomShaderMaterial from './vanilla' -import { iCSMProps } from './types' - -export default forwardRef( - ({ baseMaterial, fragmentShader, vertexShader, uniforms, ...rest }, ref) => { - const material = useMemo( - () => new CustomShaderMaterial(baseMaterial, fragmentShader, vertexShader, uniforms), - [baseMaterial, fragmentShader, vertexShader, uniforms] - ) - return - } -) diff --git a/src/CSM/keywords.ts b/src/CSM/keywords.ts deleted file mode 100644 index 9366e40..0000000 --- a/src/CSM/keywords.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default { - positon: 'csm_Position', - emissive: 'csm_Emissive', - normal: 'csm_Normal', - pointSize: 'csm_PointSize', - diffuseColor: 'csm_DiffuseColor', - fragColor: 'csm_FragColor', -} diff --git a/src/CSM/patchMaps.ts b/src/CSM/patchMaps.ts deleted file mode 100644 index ffc20bf..0000000 --- a/src/CSM/patchMaps.ts +++ /dev/null @@ -1,42 +0,0 @@ -import keywords from './keywords' - -export const VERT = { - [`${keywords.normal}`]: { - '#include ': ` - vec3 objectNormal = ${keywords.normal}; - #ifdef USE_TANGENT - vec3 objectTangent = vec3( tangent.xyz ); - #endif - `, - }, - [`${keywords.positon}`]: { - '#include ': ` - vec3 transformed = ${keywords.positon}; - `, - }, - [`${keywords.pointSize}`]: { - 'gl_PointSize = size;': ` - gl_PointSize = ${keywords.pointSize}; - `, - }, -} - -export const FRAG = { - [`${keywords.diffuseColor}`]: { - '#include ': ` - #include - diffuseColor = ${keywords.diffuseColor}; - `, - }, - [`${keywords.fragColor}`]: { - '#include ': ` - #include - gl_FragColor = ${keywords.fragColor}; - `, - }, - [`${keywords.emissive}`]: { - 'vec3 totalEmissiveRadiance = emissive;': ` - vec3 totalEmissiveRadiance = ${keywords.emissive}; - `, - }, -} diff --git a/src/CSM/types.ts b/src/CSM/types.ts deleted file mode 100644 index a35644a..0000000 --- a/src/CSM/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as THREE from 'three' -import * as FIBER from '@react-three/fiber' - -export type AllMaterialParams = THREE.MeshPhongMaterialParameters & - THREE.MeshPhysicalMaterialParameters & - THREE.MeshToonMaterialParameters & - THREE.MeshBasicMaterialParameters & - THREE.MeshLambertMaterialParameters & - THREE.MeshStandardMaterialParameters & - THREE.PointsMaterialParameters - -export type AllMaterialProps = FIBER.MeshPhongMaterialProps & // - FIBER.MeshPhysicalMaterialProps & - FIBER.MeshToonMaterialProps & - FIBER.MeshBasicMaterialProps & - FIBER.MeshLambertMaterialProps & - FIBER.MeshStandardMaterialProps & - FIBER.PointsMaterialProps - -export interface iCSMShader { - defines: string - header: string - main: string -} - -export type iCSMProps = { - baseMaterial: new () => THREE.Material - vertexShader?: string - fragmentShader?: string - uniforms?: { [key: string]: THREE.IUniform } -} & AllMaterialProps diff --git a/src/CSM/vanilla.ts b/src/CSM/vanilla.ts deleted file mode 100644 index 7df091d..0000000 --- a/src/CSM/vanilla.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { IUniform, Material, MathUtils } from 'three' -import { AllMaterialParams, iCSMProps, iCSMShader } from './types' - -import * as PATCH_MAP from './patchMaps' - -export default class CustomShaderMaterial extends Material { - uniforms: { [key: string]: IUniform } - private base: Material - - constructor( - baseMaterial: new () => Material, - fragmentShader?: string, - vertexShader?: string, - uniforms?: { [key: string]: THREE.IUniform }, - opts?: AllMaterialParams - ) { - // @ts-ignore - const base = new baseMaterial(opts) - super() - this.base = base - - this.uniforms = uniforms || {} - - for (const key in this.base) { - let k = key - if (key.startsWith('_')) { - k = key.split('_')[1] - } - - // @ts-ignore - if (this[k] === undefined) this[k] = 0 - // @ts-ignore - this[k] = this.base[k] - } - - this.update(fragmentShader, vertexShader, uniforms) - } - - update( - fragmentShader: iCSMProps['fragmentShader'], - vertexShader: iCSMProps['vertexShader'], - uniforms: iCSMProps['uniforms'] - ) { - this.generateMaterial(fragmentShader, vertexShader, uniforms) - } - - private generateMaterial( - fragmentShader: iCSMProps['fragmentShader'], - vertexShader: iCSMProps['vertexShader'], - uniforms: iCSMProps['uniforms'] - ) { - const parsedFragmentShdaer = this.parseShader(fragmentShader) - const parsedVertexShdaer = this.parseShader(vertexShader) - - this.uniforms = uniforms || {} - this.customProgramCacheKey = () => { - return this.uuid - } - - this.onBeforeCompile = (shader) => { - if (parsedFragmentShdaer) { - const patchedFragmentShdaer = this.patchShader(parsedFragmentShdaer, shader.fragmentShader, PATCH_MAP.FRAG) - shader.fragmentShader = patchedFragmentShdaer - } - if (parsedVertexShdaer) { - const patchedVertexShdaer = this.patchShader(parsedVertexShdaer, shader.vertexShader, PATCH_MAP.VERT) - - shader.vertexShader = '#define IS_VERTEX;\n' + patchedVertexShdaer - } - - shader.uniforms = { ...shader.uniforms, ...this.uniforms } - this.uniforms = shader.uniforms - this.needsUpdate = true - } - } - - private patchShader( - customShader: iCSMShader, - shader: string, - patchMap: { - [key: string]: { - [key: string]: any - } - } - ): string { - let patchedShader: string = shader - - Object.keys(patchMap).forEach((name: string) => { - Object.keys(patchMap[name]).forEach((key) => { - if (customShader.main.includes(name)) { - patchedShader = replaceAll(patchedShader, key, patchMap[name][key]) - } - }) - }) - - patchedShader = patchedShader.replace( - 'void main() {', - ` - ${customShader.header} - void main() { - vec3 csm_Position; - vec3 csm_Normal; - vec3 csm_Emissive; - - #ifdef IS_VERTEX - csm_Position = position; - #endif - - #ifdef IS_VERTEX - csm_Normal = normal; - #endif - - #ifndef IS_VERTEX - #ifdef STANDARD - csm_Emissive = emissive; - #endif - #endif - - vec4 csm_DiffuseColor = vec4(1., 0., 0., 1.); - vec4 csm_FragColor = vec4(1., 0., 0., 1.); - float csm_PointSize = 1.; - - ${customShader.main} - ` - ) - - patchedShader = customShader.defines + patchedShader - return patchedShader - } - - private parseShader(shader?: string): iCSMShader | undefined { - if (!shader) return - const parsedShader: iCSMShader = { - defines: '', - header: '', - main: '', - } - - const main = shader.match(/^(\s*)(void\s*main\s*\(.*\)\s*).*?{[\s\S]*?^\1}\s*$/gm) - - if (main?.length) { - const mainBody = main[0].match(/{[\w\W\s\S]*}/gm) - - if (mainBody?.length) { - parsedShader.main = mainBody[0] - } - - const rest = shader.replace(main[0], '') - const defines = rest.match(/#(.*?;)/g) || [] - const header = defines.reduce((prev, curr) => prev.replace(curr, ''), rest) - - parsedShader.header = header - parsedShader.defines = defines.join('\n') - } - - return parsedShader - } -} - -const replaceAll = (str: string, find: string, rep: string) => str.split(find).join(rep) diff --git a/src/core/LayerMaterial.ts b/src/core/LayerMaterial.ts new file mode 100644 index 0000000..051088b --- /dev/null +++ b/src/core/LayerMaterial.ts @@ -0,0 +1,172 @@ +import * as THREE from 'three' +import Abstract from './layers/Abstract' +import Depth from './layers/Depth' +import Color from './layers/Color' +import Noise from './layers/Noise' +import Fresnel from './layers/Fresnel' +import Gradient from './layers/Gradient' +import Matcap from './layers/Matcap' +import Texture from './layers/Texture' +import Displace from './layers/Displace' +import Normal from './layers/Normal' +import Shader from './layers/Shader' + +import BlendModesChunk from '../chunks/BlendModes' +import NoiseChunk from '../chunks/Noise' +import HelpersChunk from '../chunks/Helpers' +import { LayerMaterialParameters, SerializedBase, SerializedLayer, ShadingType, ShadingTypes } from '../types' +import { + ColorRepresentation, + MeshBasicMaterialParameters, + MeshLambertMaterialParameters, + MeshPhongMaterialParameters, + MeshPhysicalMaterialParameters, + MeshStandardMaterialParameters, + MeshToonMaterialParameters, +} from 'three' +import CustomShaderMaterial from 'three-custom-shader-material/vanilla' + +type AllMaterialParams = + | MeshPhongMaterialParameters + | MeshPhysicalMaterialParameters + | MeshToonMaterialParameters + | MeshBasicMaterialParameters + | MeshLambertMaterialParameters + | MeshStandardMaterialParameters + +class LayerMaterial extends CustomShaderMaterial { + layers: Abstract[] = [] + lighting: ShadingType = 'basic' + + constructor({ color, alpha, lighting, layers, ...props }: LayerMaterialParameters & AllMaterialParams = {}) { + super({ + baseMaterial: ShadingTypes[lighting || 'basic'], + ...props, + }) + + const _baseColor = color || 'white' + const _alpha = alpha ?? 1 + + this.uniforms = { + u_lamina_color: { + value: typeof _baseColor === 'string' ? new THREE.Color(_baseColor).convertSRGBToLinear() : _baseColor, + }, + u_lamina_alpha: { + value: _alpha, + }, + } + + this.layers = layers || this.layers + this.lighting = lighting || this.lighting + + this.refresh() + } + + genShaders() { + let vertexVariables = '' + let fragmentVariables = '' + let vertexShader = '' + let fragmentShader = '' + let uniforms: any = {} + + this.layers + .filter((l) => l.visible) + .forEach((l) => { + // l.buildShaders(l.constructor) + + vertexVariables += l.vertexVariables + '\n' + fragmentVariables += l.fragmentVariables + '\n' + vertexShader += l.vertexShader + '\n' + fragmentShader += l.fragmentShader + '\n' + + uniforms = { + ...uniforms, + ...l.uniforms, + } + }) + + uniforms = { + ...uniforms, + ...this.uniforms, + } + + return { + uniforms, + vertexShader: ` + ${HelpersChunk} + ${NoiseChunk} + ${vertexVariables} + + void main() { + vec3 lamina_finalPosition = position; + vec3 lamina_finalNormal = normal; + + ${vertexShader} + + csm_Position = lamina_finalPosition; + csm_Normal = lamina_finalNormal; + } + `, + fragmentShader: ` + ${HelpersChunk} + ${NoiseChunk} + ${BlendModesChunk} + ${fragmentVariables} + + uniform vec3 u_lamina_color; + uniform float u_lamina_alpha; + + void main() { + vec4 lamina_finalColor = vec4(u_lamina_color, u_lamina_alpha); + + ${fragmentShader} + + csm_DiffuseColor = lamina_finalColor; + + } + `, + } + } + + refresh() { + const hashes = this.layers.map((layer) => { + if (!layer.__updateMaterial) { + layer.__updateMaterial = this.refresh.bind(this) + } + return layer.getHash() + }) + const { uniforms, fragmentShader, vertexShader } = this.genShaders() + super.update({ fragmentShader, vertexShader, uniforms }) + } + + serialize(): SerializedBase { + return { + constructor: 'LayerMaterial', + currents: this.toJSON(), + } + } + + set color(v: ColorRepresentation) { + if (this.uniforms?.u_lamina_color?.value) + this.uniforms.u_lamina_color.value = typeof v === 'string' ? new THREE.Color(v).convertSRGBToLinear() : v + } + get color() { + return this.uniforms?.u_lamina_color?.value + } + set alpha(v: number) { + this.uniforms.u_lamina_alpha.value = v + } + get alpha() { + return this.uniforms.u_lamina_alpha.value + } + + toJSON(meta?: any) { + return { + ...super.toJSON(), + lighting: this.lighting, + name: this.name, + } + } +} + +export { LayerMaterial, Abstract, Depth, Color, Noise, Fresnel, Gradient, Matcap, Texture, Displace, Normal, Shader } diff --git a/src/core/Loader.ts b/src/core/Loader.ts new file mode 100644 index 0000000..fde0cdf --- /dev/null +++ b/src/core/Loader.ts @@ -0,0 +1,97 @@ +import * as THREE from 'three' +import { LayerMaterial, Abstract, Shader } from './LayerMaterial' +import { LaminaLayerFile, LaminaMaterialFile } from '../types' + +class ImportedLayer extends Abstract { + constructor(props?: any) { + super(ImportedLayer, props) + } +} + +function isBase64UrlImage(s: string) { + return s.trim().startsWith('data:image') +} + +export class LaminaLoader extends THREE.Loader { + texLoader: THREE.TextureLoader + + constructor(manager?: THREE.LoadingManager) { + super(manager) + this.texLoader = new THREE.TextureLoader() + } + + load( + url: string, + onLoad?: (event: T) => void, + onError?: (event: Error) => void + ): void { + fetch(url) + .then((resp) => + resp + .json() + .then(async (json: T extends LayerMaterial ? LaminaMaterialFile : LaminaLayerFile) => { + if (json.metadata.type === 'mat') { + const data = json as LaminaMaterialFile + + const layers = await Promise.all( + data.layers.map(async (layer) => { + const l = new Abstract(ImportedLayer) + l.raw.fragment = layer.fragment + l.raw.vertex = layer.vertex + l.raw.uniforms = layer.uniforms + l.raw.nonUniforms = layer.nonUniforms + + l.onShaderParse = new Function(`return (${layer.functions.onShaderParse})`)() + l.onNonUniformsParse = new Function(`return (${layer.functions.onNonUniformsParse})`)() + l.onUniformsParse = new Function(`return (${layer.functions.onUniformsParse})`)() + + l.buildUniforms() + l.buildNonUniforms() + l.buildShaders() + + await Promise.all( + Object.entries(layer.currents).map(async ([key, val]) => { + if (typeof val === 'string' && isBase64UrlImage(val)) { + const t = await this.texLoader.loadAsync(val) + t.encoding = THREE.sRGBEncoding + //@ts-ignore + l[key] = t + } else { + //@ts-ignore + l[key] = val + } + }) + ) + + return l + }) + ) + + delete data.base.currents.metadata + const mat = new LayerMaterial({ + ...data.base.currents, + layers, + }) + + onLoad?.(mat as T) + } + }) + .catch((e) => onError?.(e)) + ) + .catch((e) => onError?.(e)) + } + + loadAsync(url: string): Promise { + return new Promise((resolve, reject) => { + this.load( + url, + (e) => { + resolve(e) + }, + (err) => { + reject(err) + } + ) + }) + } +} diff --git a/src/core/debugger/index.tsx b/src/core/debugger/index.tsx new file mode 100644 index 0000000..ee0940f --- /dev/null +++ b/src/core/debugger/index.tsx @@ -0,0 +1,32 @@ +import * as React from 'react' +import * as THREE from 'three' + +import { createRoot } from 'react-dom/client' + +import { AllMaterialProps } from 'three-custom-shader-material/types' +import { LaminaMaterialFile, LayerMaterialProps } from '../../types' + +import { LayerMaterial as LayerMaterialType } from '../../vanilla' +import { button, LevaPanel, useControls, useCreateStore } from 'leva' +import { useRef } from 'react' +import mergeRefs from 'react-merge-refs' +import { downloadObjectAsJson, serializedLayersToJSX } from '../../utils/ExportUtils' +import useExports from './useExports' +import useAttach from './useAttach' + +export const DebugLayerMaterial = React.forwardRef< + LayerMaterialType, + React.PropsWithChildren> +>(({ children, ...props }, forwardRef) => { + const ref = useRef(null!) + const store = useCreateStore() + + useExports(ref, store) + useAttach(ref, store) + + if (!Array.isArray(children)) { + return <>{React.cloneElement(children as React.ReactElement, { ref: mergeRefs([forwardRef, ref]) })} + } else { + throw new Error('Lamina Debugger must contain only one as child.') + } +}) diff --git a/src/core/debugger/useAttach.tsx b/src/core/debugger/useAttach.tsx new file mode 100644 index 0000000..fa77af6 --- /dev/null +++ b/src/core/debugger/useAttach.tsx @@ -0,0 +1,34 @@ +import * as React from 'react' +import { createRoot } from 'react-dom/client' +import { LevaPanel } from 'leva' +import { LayerMaterial } from '../../vanilla' +import { StoreType } from 'leva/dist/declarations/src/types' + +export default function useAttach(ref: React.MutableRefObject, store: StoreType) { + React.useLayoutEffect(() => { + let root = document.body.querySelector('#root') + if (!root) { + root = document.createElement('div') + root.id = 'root' + document.body.appendChild(root) + } + const div = document.createElement('div') + + if (root) { + root.appendChild(div) + const levaRoot = createRoot(div) + levaRoot.render( + + ) + } + + return () => { + div.remove() + } + }, []) +} diff --git a/src/core/debugger/useExports.ts b/src/core/debugger/useExports.ts new file mode 100644 index 0000000..232de9e --- /dev/null +++ b/src/core/debugger/useExports.ts @@ -0,0 +1,32 @@ +import * as React from 'react' +import { button, useControls } from 'leva' +import { downloadObjectAsJson, serializedLayersToJSX } from '../../utils/ExportUtils' +import { LayerMaterial } from '../../vanilla' +import { LaminaMaterialFile } from '../../types' +import { StoreType } from 'leva/dist/declarations/src/types' + +export default function useExports(ref: React.MutableRefObject, store: StoreType) { + useControls( + { + 'Copy JSX': button(() => { + const serialized = ref.current.layers.map((l) => l.serialize()) + const jsx = serializedLayersToJSX(serialized, ref.current.serialize()) + navigator.clipboard.writeText(jsx) + }), + Export: button(() => { + const serialized = ref.current.layers.map((l) => l.serialize()) + const serializedBase = ref.current.serialize() + const file: LaminaMaterialFile = { + metadata: { + version: 1, + type: 'mat', + }, + base: serializedBase, + layers: serialized, + } + downloadObjectAsJson(file, serializedBase.currents?.name) + }), + }, + { store } + ) +} diff --git a/src/index.tsx b/src/core/index.tsx similarity index 90% rename from src/index.tsx rename to src/core/index.tsx index c0c55e2..68ac31c 100644 --- a/src/index.tsx +++ b/src/core/index.tsx @@ -23,20 +23,14 @@ import { NormalProps, LayerMaterialParameters, ShaderProps, -} from './types' -import * as LAYERS from './vanilla' -import DebugLayerMaterial from './debug' -import { getLayerMaterialArgs } from './utils/Functions' -import { ColorRepresentation } from 'three' +} from '../types' +import * as LAYERS from '../vanilla' import { useEffect } from 'react' -import { useRef } from 'react' -import { useLayoutEffect } from 'react' declare global { namespace JSX { interface IntrinsicElements { layerMaterial: Node - debuglayerMaterial: Node depth_: Node color_: Node noise_: Node @@ -138,17 +132,4 @@ const Shader = React.forwardRef((props, ref) => { return }) as React.ForwardRefExoticComponent> -export { - DebugLayerMaterial, - LayerMaterial, - Depth, - Color, - Noise, - Fresnel, - Gradient, - Matcap, - Texture, - Displace, - Normal, - Shader, -} +export { LayerMaterial, Depth, Color, Noise, Fresnel, Gradient, Matcap, Texture, Displace, Normal, Shader } diff --git a/src/core/Abstract.ts b/src/core/layers/Abstract.ts similarity index 90% rename from src/core/Abstract.ts rename to src/core/layers/Abstract.ts index b240d9c..b9aa58b 100644 --- a/src/core/Abstract.ts +++ b/src/core/layers/Abstract.ts @@ -1,6 +1,6 @@ -import { getSpecialParameters, getUniform, isSerializableType, serializeProp } from '../utils/Functions' +import { getSpecialParameters, getUniform, isSerializableType, serializeProp } from '../../utils/Functions' import { Color, IUniform, MathUtils, Texture, Vector3 } from 'three' -import { BlendMode, BlendModes, LayerProps, SerializedLayer } from '../types' +import { BlendMode, BlendModes, LayerProps, SerializedLayer } from '../../types' import hash from 'object-hash' // @ts-ignore @@ -105,7 +105,6 @@ export default class Abstract { } init() { - console.log('init') const defaults = Object.getOwnPropertyNames(this.raw.constructor) defaults.forEach((v) => { @@ -139,7 +138,6 @@ export default class Abstract { } buildUniforms() { - console.log('buildUniforms') const properties: PropertyDescriptorMap & ThisType = {} Object.keys(this.raw.uniforms).map((propName) => { // @ts-ignore @@ -347,44 +345,38 @@ export default class Abstract { serialize(): SerializedLayer { const name = this.constructor.name.split('$')[0] - let nonUniformPropKeys = Object.keys(this) - nonUniformPropKeys = nonUniformPropKeys.filter( - (e) => - ![ - 'uuid', - 'uniforms', - 'schema', - 'fragmentShader', - 'vertexShader', - 'fragmentVariables', - 'vertexVariables', - 'attribs', - 'events', - '__r3f', - 'onParse', - ].includes(e) - ) - const nonUniformProps = {} - nonUniformPropKeys.forEach((k) => { - // @ts-ignore - nonUniformProps[k] = this[k] + + const uniforms: { [key: string]: any } = {} + Object.entries(this.raw.uniforms).forEach(([key, value]) => { + uniforms[key] = serializeProp(value) }) - const props: { [key: string]: any } = {} - for (const key in this.uniforms) { - const name = key.replace(`u_${this.uuid}_`, '') - props[name] = serializeProp(this.uniforms[key].value) - } + const nonUniforms: { [key: string]: any } = {} + Object.entries(this.raw.nonUniforms).forEach(([key, value]) => { + nonUniforms[key] = serializeProp(value) + }) + + const currents: { [key: string]: any } = {} + const allValueKeys = [...Object.keys(uniforms), ...Object.keys(nonUniforms)] + allValueKeys + // @ts-ignore + .map((key) => this[key]) + .forEach((value, i) => { + const key = allValueKeys[i] + currents[key] = serializeProp(value) + }) return { constructor: name, - properties: { - ...props, - ...nonUniformProps, - }, - shaders: { - fragment: this.raw.fragment, - vertex: this.raw.vertex, + fragment: this.raw.fragment, + vertex: this.raw.vertex, + uniforms: uniforms, + nonUniforms: nonUniforms, + currents: currents, + functions: { + onShaderParse: this.onShaderParse?.toString(), + onNonUniformsParse: this.onNonUniformsParse?.toString(), + onUniformsParse: this.onUniformsParse?.toString(), }, } } diff --git a/src/core/Color.ts b/src/core/layers/Color.ts similarity index 90% rename from src/core/Color.ts rename to src/core/layers/Color.ts index 2bd7eaf..c291afc 100644 --- a/src/core/Color.ts +++ b/src/core/layers/Color.ts @@ -1,4 +1,4 @@ -import { ColorProps } from '../types' +import { ColorProps } from '../../types' import Abstract from './Abstract' export default class Color extends Abstract { diff --git a/src/core/Depth.ts b/src/core/layers/Depth.ts similarity index 74% rename from src/core/Depth.ts rename to src/core/layers/Depth.ts index 92c4843..d7270ca 100644 --- a/src/core/Depth.ts +++ b/src/core/layers/Depth.ts @@ -1,5 +1,5 @@ import { Vector3 } from 'three' -import { DepthProps } from '../types' +import { DepthProps } from '../../types' import Abstract from './Abstract' type AbstractExtended = Abstract & { @@ -53,28 +53,28 @@ export default class Depth extends Abstract { name: 'Depth', ...props, onShaderParse: (self) => { + function getMapping(uuid: string, type?: string) { + switch (type) { + default: + case 'vector': + return `length(v_${uuid}_worldPosition - u_${uuid}_origin)` + case 'world': + return `length(v_${uuid}_position - vec3(0.))` + case 'camera': + return `length(v_${uuid}_worldPosition - cameraPosition)` + } + } + self.schema.push({ value: self.mapping, label: 'mapping', options: ['vector', 'world', 'camera'], }) - const mapping = Depth.getMapping(self.uuid, self.mapping) + const mapping = getMapping(self.uuid, self.mapping) self.fragmentShader = self.fragmentShader.replace('lamina_mapping_template', mapping) }, }) } - - private static getMapping(uuid: string, type?: string) { - switch (type) { - default: - case 'vector': - return `length(v_${uuid}_worldPosition - u_${uuid}_origin)` - case 'world': - return `length(v_${uuid}_position - vec3(0.))` - case 'camera': - return `length(v_${uuid}_worldPosition - cameraPosition)` - } - } } diff --git a/src/core/Displace.ts b/src/core/layers/Displace.ts similarity index 98% rename from src/core/Displace.ts rename to src/core/layers/Displace.ts index 3858a05..eb02834 100644 --- a/src/core/Displace.ts +++ b/src/core/layers/Displace.ts @@ -1,5 +1,5 @@ import { Vector3 } from 'three' -import { ColorProps, DisplaceProps, MappingType, MappingTypes, NoiseProps, NoiseType, NoiseTypes } from '../types' +import { ColorProps, DisplaceProps, MappingType, MappingTypes, NoiseProps, NoiseType, NoiseTypes } from '../../types' import Abstract from './Abstract' type AbstractExtended = Abstract & { diff --git a/src/core/Fresnel.ts b/src/core/layers/Fresnel.ts similarity index 96% rename from src/core/Fresnel.ts rename to src/core/layers/Fresnel.ts index 632fa57..43b632e 100644 --- a/src/core/Fresnel.ts +++ b/src/core/layers/Fresnel.ts @@ -1,4 +1,4 @@ -import { FresnelProps } from '../types' +import { FresnelProps } from '../../types' import Abstract from './Abstract' export default class Fresnel extends Abstract { diff --git a/src/core/Gradient.ts b/src/core/layers/Gradient.ts similarity index 96% rename from src/core/Gradient.ts rename to src/core/layers/Gradient.ts index 5149378..bd6bcfa 100644 --- a/src/core/Gradient.ts +++ b/src/core/layers/Gradient.ts @@ -1,5 +1,5 @@ import { Vector3 } from 'three' -import { GradientProps, MappingType, MappingTypes } from '../types' +import { GradientProps, MappingType, MappingTypes } from '../../types' import Abstract from './Abstract' export default class Gradient extends Abstract { diff --git a/src/core/Matcap.ts b/src/core/layers/Matcap.ts similarity index 84% rename from src/core/Matcap.ts rename to src/core/layers/Matcap.ts index 7124e67..1bb821f 100644 --- a/src/core/Matcap.ts +++ b/src/core/layers/Matcap.ts @@ -1,11 +1,11 @@ -import { MatcapProps } from "../types"; -import Abstract from "./Abstract"; +import { MatcapProps } from '../../types' +import Abstract from './Abstract' // Credits: https://www.clicktorelease.com/blog/creating-spherical-environment-mapping-shader/ export default class Matcap extends Abstract { - static u_alpha = 1; - static u_map = undefined; + static u_alpha = 1 + static u_map = undefined static vertexShader = ` varying vec3 v_position; @@ -15,7 +15,7 @@ export default class Matcap extends Abstract { v_position = normalize( vec3( modelViewMatrix * vec4( position, 1.0 ) ) ); v_normal = normalize( normalMatrix * normal ); } - `; + ` static fragmentShader = ` uniform sampler2D u_map; @@ -33,12 +33,12 @@ export default class Matcap extends Abstract { return vec4(f_base, u_alpha); } - `; + ` constructor(props?: MatcapProps) { super(Matcap, { - name: "Matcap", + name: 'Matcap', ...props, - }); + }) } } diff --git a/src/core/Noise.ts b/src/core/layers/Noise.ts similarity index 62% rename from src/core/Noise.ts rename to src/core/layers/Noise.ts index bcb6a39..50be529 100644 --- a/src/core/Noise.ts +++ b/src/core/layers/Noise.ts @@ -1,12 +1,7 @@ import { Vector3 } from 'three' -import { ColorProps, MappingType, MappingTypes, NoiseProps, NoiseType, NoiseTypes } from '../types' +import { MappingType, MappingTypes, NoiseProps, NoiseType, NoiseTypes } from '../../types' import Abstract from './Abstract' -type AbstractExtended = Abstract & { - type: NoiseType - mapping: MappingType -} - export default class Noise extends Abstract { static u_colorA = '#666666' static u_colorB = '#666666' @@ -62,52 +57,52 @@ export default class Noise extends Abstract { name: 'noise', ...props, onShaderParse: (self) => { + function getNoiseFunction(type?: string) { + switch (type) { + default: + case 'perlin': + return `lamina_noise_perlin` + case 'simplex': + return `lamina_noise_simplex` + case 'cell': + return `lamina_noise_worley` + case 'white': + return `lamina_noise_white` + case 'curl': + return `lamina_noise_swirl` + } + } + + function getMapping(type?: string) { + switch (type) { + default: + case 'local': + return `position` + case 'world': + return `(modelMatrix * vec4(position,1.0)).xyz` + case 'uv': + return `vec3(uv, 0.)` + } + } + self.schema.push({ value: self.type, label: 'type', - options: Object.values(NoiseTypes), + options: ['perlin', 'simplex', 'cell', 'curl', 'white'], }) self.schema.push({ value: self.mapping, label: 'mapping', - options: Object.values(MappingTypes), + options: ['local', 'world', 'uv'], }) - const noiseFunc = Noise.getNoiseFunction(self.type) - const mapping = Noise.getMapping(self.mapping) + const noiseFunc = getNoiseFunction(self.type) + const mapping = getMapping(self.mapping) self.vertexShader = self.vertexShader.replace('lamina_mapping_template', mapping) self.fragmentShader = self.fragmentShader.replace('lamina_noise_template', noiseFunc) }, }) } - - private static getNoiseFunction(type?: string) { - switch (type) { - default: - case 'perlin': - return `lamina_noise_perlin` - case 'simplex': - return `lamina_noise_simplex` - case 'cell': - return `lamina_noise_worley` - case 'white': - return `lamina_noise_white` - case 'curl': - return `lamina_noise_swirl` - } - } - - private static getMapping(type?: string) { - switch (type) { - default: - case 'local': - return `position` - case 'world': - return `(modelMatrix * vec4(position,1.0)).xyz` - case 'uv': - return `vec3(uv, 0.)` - } - } } diff --git a/src/core/Normal.ts b/src/core/layers/Normal.ts similarity index 94% rename from src/core/Normal.ts rename to src/core/layers/Normal.ts index dd2a8f7..b3cbd88 100644 --- a/src/core/Normal.ts +++ b/src/core/layers/Normal.ts @@ -1,5 +1,5 @@ import { Vector3 } from 'three' -import { NormalProps } from '../types' +import { NormalProps } from '../../types' import Abstract from './Abstract' export default class Normal extends Abstract { diff --git a/src/core/Shader.ts b/src/core/layers/Shader.ts similarity index 93% rename from src/core/Shader.ts rename to src/core/layers/Shader.ts index 2fd2a3e..648c87f 100644 --- a/src/core/Shader.ts +++ b/src/core/layers/Shader.ts @@ -1,5 +1,4 @@ -import { ShaderProps } from '../types' -import { getUniform } from '../utils/Functions' +import { ShaderProps } from '../../types' import Abstract from './Abstract' export default class Shader extends Abstract { diff --git a/src/core/Texture.ts b/src/core/layers/Texture.ts similarity index 88% rename from src/core/Texture.ts rename to src/core/layers/Texture.ts index 7e1f859..b687390 100644 --- a/src/core/Texture.ts +++ b/src/core/layers/Texture.ts @@ -1,9 +1,9 @@ -import { TextureProps } from '../types' +import { TextureProps } from '../../types' import Abstract from './Abstract' export default class Texture extends Abstract { static u_alpha = 1 - static u_map = undefined + static u_map = null static vertexShader = ` varying vec2 v_uv; diff --git a/src/core/layers/index.ts b/src/core/layers/index.ts new file mode 100644 index 0000000..7ac169e --- /dev/null +++ b/src/core/layers/index.ts @@ -0,0 +1,23 @@ +import _Abstract from './Abstract' +import _Depth from './Depth' +import _Color from './Color' +import _Noise from './Noise' +import _Fresnel from './Fresnel' +import _Gradient from './Gradient' +import _Matcap from './Matcap' +import _Texture from './Texture' +import _Displace from './Displace' +import _Normal from './Normal' +import _Shader from './Shader' + +export const Abstract = _Abstract +export const Depth = _Depth +export const Color = _Color +export const Noise = _Noise +export const Fresnel = _Fresnel +export const Gradient = _Gradient +export const Matcap = _Matcap +export const Texture = _Texture +export const Displace = _Displace +export const Normal = _Normal +export const Shader = _Shader diff --git a/src/core/useLoader.tsx b/src/core/useLoader.tsx new file mode 100644 index 0000000..fd79685 --- /dev/null +++ b/src/core/useLoader.tsx @@ -0,0 +1,33 @@ +import * as React from 'react' +import * as LAYERS from '../vanilla' + +import mergeRefs from 'react-merge-refs' +import { useLoader } from '@react-three/fiber' + +import { LayerMaterial } from '.' +import { LaminaLoader } from './Loader' +import { LayerMaterialProps, LayerProps } from '../types' + +export function useLamina(url: string) { + // @ts-ignore + const material = useLoader(LaminaLoader, url) + + return material instanceof LAYERS.LayerMaterial + ? React.forwardRef>>( + (props, forwardRef) => { + const ref = React.useRef(null!) + + return ( + + {material.layers.map((e) => ( + + ))} + {props.children} + + ) + } + ) + : React.forwardRef((props, ref) => { + return + }) +} diff --git a/src/debug.ts b/src/debug.ts new file mode 100644 index 0000000..d0cae4b --- /dev/null +++ b/src/debug.ts @@ -0,0 +1 @@ +export * from './core/debugger' diff --git a/src/debug.tsx b/src/debug.tsx deleted file mode 100644 index 99c2823..0000000 --- a/src/debug.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { - extend, - MeshPhongMaterialProps, - MeshPhysicalMaterialProps, - MeshToonMaterialProps, - MeshBasicMaterialProps, - MeshLambertMaterialProps, - MeshStandardMaterialProps, -} from '@react-three/fiber' -import { createRoot } from 'react-dom/client' - -import { button, LevaPanel, useControls, useCreateStore } from 'leva' -import { DataItem, StoreType } from 'leva/dist/declarations/src/types' -import React, { useEffect, useMemo, useState } from 'react' -import mergeRefs from 'react-merge-refs' -import { getLayerMaterialArgs, getUniform } from './utils/Functions' -import { serializedLayersToJSX, serializedLayersToJS } from './utils/ExportUtils' -import * as LAYERS from './vanilla' -import { Color, ColorRepresentation, TextureLoader } from 'three' -import { LayerMaterialProps, ShadingType, ShadingTypes } from './types' - -extend({ - LayerMaterial: LAYERS.LayerMaterial, -}) - -function DynamicLeva({ - name, - layers, - store, - setUpdate, -}: { - setUpdate: any - name: string - layers: any[] - store: StoreType -}) { - useControls( - name, - () => { - const o: any = {} - layers.forEach((layer, i: number) => { - const n = `${layer.label} ~${i}` - o[n] = layer - o[n].onChange = () => setUpdate([`${name}.${n}`, layer.label]) - }) - return o - }, - { store }, - [layers, name] - ) - - return null -} - -type AllMaterialProps = MeshPhongMaterialProps & // - MeshPhysicalMaterialProps & - MeshToonMaterialProps & - MeshBasicMaterialProps & - MeshLambertMaterialProps & - MeshStandardMaterialProps - -const DebugLayerMaterial = React.forwardRef< - LAYERS.LayerMaterial, - React.PropsWithChildren> ->(({ children, ...props }, forwardRef) => { - const ref = React.useRef< - LAYERS.LayerMaterial & { - [key: string]: any - } - >(null!) - const store = useCreateStore() - const [layers, setLayers] = React.useState<{ [name: string]: any[] }>({}) - const [path, setPath] = React.useState(['', '']) - const textureLoader = useMemo(() => new TextureLoader(), []) - - // useControls( - // { - // 'Copy JSX': button(() => { - // const serialized = ref.current.layers.map((l) => l.serialize()) - // const jsx = serializedLayersToJSX(serialized, ref.current.serialize()) - // navigator.clipboard.writeText(jsx) - // }), - // }, - // { store } - // ) - - const { Lighting } = useControls( - 'Base', - { - Color: { - value: '#' + new Color(ref.current?.color || props?.color || 'white').convertLinearToSRGB().getHexString(), - onChange: (v) => { - ref.current.color = v - }, - }, - Alpha: { - value: ref.current?.alpha || props?.alpha || 1, - min: 0, - max: 1, - onChange: (v) => { - ref.current.alpha = v - }, - }, - Lighting: { - value: ref.current?.lighting || props?.lighting || 'basic', - options: Object.keys(ShadingTypes), - }, - }, - { store } - ) - const [args, otherProps] = useMemo(() => getLayerMaterialArgs({ ...props, lighting: Lighting }), [props, Lighting]) - - React.useEffect(() => { - const layers = ref.current.layers - - const schema: { [name: string]: any[] } = {} - layers.forEach((layer: any, i: number) => { - if (layer.getSchema) schema[`${layer.name} ~${i}`] = layer.getSchema() - }) - - setLayers(schema) - }, [children]) - - React.useEffect(() => { - const data = store.getData() - const updatedData = data[path[0]] as DataItem & { - value: any - } - if (updatedData) { - const split = path[0].split('.') - const index = parseInt(split[0].split(' ~')[1]) - const property = path[1] - const id = ref.current.layers[index].uuid - const uniform = ref.current.uniforms[`u_${id}_${property}`] - const layer = ref.current.layers[index] as LAYERS.Abstract & { - [key: string]: any - } - console.log(property) - - // if (property !== 'map') { - // layer[property] = updatedData.value - // if (uniform) { - // uniform.value = getUniform(updatedData.value) - // } else { - // layer.buildShaders(layer.raw.constructor) - // ref.current.refresh() - // } - // } else { - // ;(async () => { - // try { - // if (updatedData.value) { - // const t = await textureLoader.loadAsync(updatedData.value) - // layer[property] = t - // uniform.value = t - // } else { - // layer[property] = undefined - // uniform.value = undefined - // } - // } catch (error) { - // console.error(error) - // } - // })() - // } - } - }, [path]) - - React.useLayoutEffect(() => { - ref.current.layers = (ref.current as any).__r3f.objects - ref.current.refresh() - }, [children, args]) - - React.useLayoutEffect(() => { - const root = document.body.querySelector('#root') - const div = document.createElement('div') - - if (root) { - root.appendChild(div) - const levaRoot = createRoot(div) - levaRoot.render( - - ) - } - - return () => { - div.remove() - } - }, [props.name]) - - return ( - <> - {Object.entries(layers).map(([name, layers], i) => ( - - ))} - - {children} - - - ) -}) - -export default DebugLayerMaterial diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..49eb534 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export * from './core' +export * from './core/useLoader' diff --git a/src/types.ts b/src/types.ts index 0e0ea48..dd779fb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -173,11 +173,44 @@ export interface ShaderProps extends LayerProps { export interface SerializedLayer { constructor: string - properties: { - [name: string]: any + fragment: string + vertex: string + uniforms: { + [key: string]: any } - shaders: { - fragment: string - vertex: string + nonUniforms: { + [key: string]: any } + currents: { + [key: string]: any + } + functions: { + onShaderParse?: string + onNonUniformsParse?: string + onUniformsParse?: string + } +} + +export interface SerializedBase { + constructor: string + currents: { + [key: string]: any + } +} + +export interface LaminaMaterialFile { + metadata: { + version: number + type: 'mat' + } + base: SerializedBase + layers: SerializedLayer[] +} + +export interface LaminaLayerFile { + metadata: { + version: number + type: 'layer' + } + base: SerializedLayer } diff --git a/src/utils/ExportUtils.ts b/src/utils/ExportUtils.ts index 6eb003a..44e84ab 100644 --- a/src/utils/ExportUtils.ts +++ b/src/utils/ExportUtils.ts @@ -1,12 +1,13 @@ -import { SerializedLayer } from 'src/types' +import { LaminaLayerFile, LaminaMaterialFile, SerializedLayer } from '../types' import * as LAYERS from '../vanilla' -function getPropsFromLayer(layer: SerializedLayer) { +function getPropsFromLayer(layer: Partial) { // @ts-ignore const constructor = LAYERS[layer.constructor] const instance = new constructor() let props = '' - Object.entries(layer.properties).forEach(([key, val]) => { + // @ts-ignore + Object.entries(layer.properties || {}).forEach(([key, val]) => { const defaultVal = constructor['u_' + key] ?? instance[key] switch (key) { @@ -27,7 +28,7 @@ function getPropsFromLayer(layer: SerializedLayer) { return props } -export function serializedLayersToJSX(layers: SerializedLayer[], material: SerializedLayer) { +export function serializedLayersToJSX(layers: SerializedLayer[], material: Partial) { const materialProps = getPropsFromLayer(material) const jsx = ` @@ -46,43 +47,47 @@ export function serializedLayersToJSX(layers: SerializedLayer[], material: Seria function getJSPropsFromLayer(layer: SerializedLayer) { // @ts-ignore - const constructor = LAYERS[layer.constructor]; - const instance = new constructor(); - let props = '\t'; - let entries = Object.entries(layer.properties); + const constructor = LAYERS[layer.constructor] + const instance = new constructor() + let props = '\t' + // @ts-ignore + let entries = Object.entries(layer.properties) entries.forEach(([key, val], idx) => { - var _constructor; - const eol = '\n\t\t'; + var _constructor + const eol = '\n\t\t' if (key.includes('color')) { - const v = typeof val === "string" ? val : '#' + val.getHexString(); - props += `${key}: ${JSON.stringify(v)},${eol}`; + // @ts-ignore + const v = typeof val === 'string' ? val : '#' + val.getHexString() + props += `${key}: ${JSON.stringify(v)},${eol}` } else { - const defaultVal = (_constructor = constructor['u_' + key]) != null ? _constructor : instance[key]; + const defaultVal = (_constructor = constructor['u_' + key]) != null ? _constructor : instance[key] switch (key) { case 'name': - if (val !== layer.constructor) props += `${key}: ${JSON.stringify(val)},${eol}`; - break; - - case 'visible': - if (!val) props += `${key}:${JSON.stringify(val)},${eol}`; - break; - - default: - if (val !== defaultVal) props += `${key}: ${JSON.stringify(val)},${eol}`; - break; - } + if (val !== layer.constructor) props += `${key}: ${JSON.stringify(val)},${eol}` + break + + case 'visible': + if (!val) props += `${key}:${JSON.stringify(val)},${eol}` + break + + default: + if (val !== defaultVal) props += `${key}: ${JSON.stringify(val)},${eol}` + break } - }); - return props; + } + }) + return props } -export function serializedLayersToJS(layers: SerializedLayer[], material: SerializedLayer){ - const materialProps = getJSPropsFromLayer(material); - const jsLayers = `${layers.map(l => { - return `new ${l.constructor}({ +export function serializedLayersToJS(layers: SerializedLayer[], material: SerializedLayer) { + const materialProps = getJSPropsFromLayer(material) + const jsLayers = `${layers + .map((l) => { + return `new ${l.constructor}({ ${getJSPropsFromLayer(l)} })` - }).join(',\n\t\t')}` + }) + .join(',\n\t\t')}` const js = ` new LayerMaterial({ @@ -92,5 +97,72 @@ export function serializedLayersToJS(layers: SerializedLayer[], material: Serial ] })` - return js; + return js +} + +function isValidHttpUrl(string: string) { + let url + + try { + url = new URL(string) + } catch (_) { + return false + } + + return url.protocol === 'http:' || url.protocol === 'https:' +} + +async function toDataURL(url: string) { + var xhr = new XMLHttpRequest() + + xhr.open('GET', url) + xhr.responseType = 'blob' + xhr.send() + + return new Promise((res, rej) => { + xhr.onload = function () { + var reader = new FileReader() + reader.onloadend = function () { + res(reader.result) + } + reader.readAsDataURL(xhr.response) + } + }) +} + +export async function downloadObjectAsJson(exportObj: LaminaMaterialFile | LaminaLayerFile, exportName: string) { + const obj = structuredClone(exportObj) + + if (obj.metadata.type === 'mat') { + const o = obj as LaminaMaterialFile + + await Promise.all( + o.layers.map(async (layer) => { + await Promise.all( + Object.entries(layer.currents).map(async ([key, val]) => { + if (isValidHttpUrl(val)) { + layer.currents[key] = await toDataURL(val) + } + }) + ) + }) + ) + } else { + const o = obj as LaminaLayerFile + await Promise.all( + Object.entries(o.base.currents).map(async ([key, val]) => { + if (isValidHttpUrl(val)) { + o.base.currents[key] = await toDataURL(val) + } + }) + ) + } + + var dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(obj)) + var downloadAnchorNode = document.createElement('a') + downloadAnchorNode.setAttribute('href', dataStr) + downloadAnchorNode.setAttribute('download', exportName + '.lamina') + document.body.appendChild(downloadAnchorNode) // required for firefox + downloadAnchorNode.click() + downloadAnchorNode.remove() } diff --git a/src/utils/Functions.ts b/src/utils/Functions.ts index 2596a6d..5fab474 100644 --- a/src/utils/Functions.ts +++ b/src/utils/Functions.ts @@ -3,7 +3,7 @@ import { LayerMaterialProps } from '../types' export function getUniform(value: any) { if (typeof value === 'string') { - return new Color(value).convertLinearToSRGB() + return new Color(value) } return value @@ -43,6 +43,10 @@ export function getLayerMaterialArgs({ color, alpha, lighting, name, ...rest }: ] as any } +function roundToTwo(num: number) { + return +Math.round((num + Number.EPSILON) * 100) / 100 +} + export function isSerializableType(prop: any) { return ( prop instanceof Vector3 || @@ -55,12 +59,12 @@ export function isSerializableType(prop: any) { export function serializeProp(prop: any) { if (isSerializableType(prop)) { - return prop.toArray() + return (prop.toArray() as number[]).map((e) => roundToTwo(e)) } else if (prop instanceof Color) { - return '#' + prop.clone().convertLinearToSRGB().getHexString() + return '#' + prop.clone().getHexString() } else if (prop instanceof Texture) { return prop.image.src } - return prop + return typeof prop === 'number' ? roundToTwo(prop) : prop } diff --git a/src/vanilla.ts b/src/vanilla.ts index c92ca25..1d3fd0c 100644 --- a/src/vanilla.ts +++ b/src/vanilla.ts @@ -1,174 +1,2 @@ -import * as THREE from 'three' -import hash from 'object-hash' - -import Abstract from './core/Abstract' -import Depth from './core/Depth' -import Color from './core/Color' -import Noise from './core/Noise' -import Fresnel from './core/Fresnel' -import Gradient from './core/Gradient' -import Matcap from './core/Matcap' -import Texture from './core/Texture' -import Displace from './core/Displace' -import Normal from './core/Normal' -import Shader from './core/Shader' - -import BlendModesChunk from './chunks/BlendModes' -import NoiseChunk from './chunks/Noise' -import HelpersChunk from './chunks/Helpers' -import { LayerMaterialParameters, SerializedLayer, ShadingType, ShadingTypes } from './types' -import { - ColorRepresentation, - MeshBasicMaterialParameters, - MeshLambertMaterialParameters, - MeshPhongMaterialParameters, - MeshPhysicalMaterialParameters, - MeshStandardMaterialParameters, - MeshToonMaterialParameters, -} from 'three' -import CustomShaderMaterial from 'three-custom-shader-material/vanilla' - -type AllMaterialParams = - | MeshPhongMaterialParameters - | MeshPhysicalMaterialParameters - | MeshToonMaterialParameters - | MeshBasicMaterialParameters - | MeshLambertMaterialParameters - | MeshStandardMaterialParameters - -class LayerMaterial extends CustomShaderMaterial { - name: string = 'LayerMaterial' - layers: Abstract[] = [] - lighting: ShadingType = 'basic' - - constructor({ color, alpha, lighting, layers, name, ...props }: LayerMaterialParameters & AllMaterialParams = {}) { - super({ - baseMaterial: ShadingTypes[lighting || 'basic'], - ...props, - }) - - const _baseColor = color || 'white' - const _alpha = alpha ?? 1 - - this.uniforms = { - u_lamina_color: { - value: typeof _baseColor === 'string' ? new THREE.Color(_baseColor).convertSRGBToLinear() : _baseColor, - }, - u_lamina_alpha: { - value: _alpha, - }, - } - - this.layers = layers || this.layers - this.lighting = lighting || this.lighting - - this.refresh() - } - - genShaders() { - console.log('genShaders') - let vertexVariables = '' - let fragmentVariables = '' - let vertexShader = '' - let fragmentShader = '' - let uniforms: any = {} - - this.layers - .filter((l) => l.visible) - .forEach((l) => { - // l.buildShaders(l.constructor) - - vertexVariables += l.vertexVariables + '\n' - fragmentVariables += l.fragmentVariables + '\n' - vertexShader += l.vertexShader + '\n' - fragmentShader += l.fragmentShader + '\n' - - uniforms = { - ...uniforms, - ...l.uniforms, - } - }) - - uniforms = { - ...uniforms, - ...this.uniforms, - } - - return { - uniforms, - vertexShader: ` - ${HelpersChunk} - ${NoiseChunk} - ${vertexVariables} - - void main() { - vec3 lamina_finalPosition = position; - vec3 lamina_finalNormal = normal; - - ${vertexShader} - - csm_Position = lamina_finalPosition; - csm_Normal = lamina_finalNormal; - } - `, - fragmentShader: ` - ${HelpersChunk} - ${NoiseChunk} - ${BlendModesChunk} - ${fragmentVariables} - - uniform vec3 u_lamina_color; - uniform float u_lamina_alpha; - - void main() { - vec4 lamina_finalColor = vec4(u_lamina_color, u_lamina_alpha); - - ${fragmentShader} - - csm_DiffuseColor = lamina_finalColor; - - } - `, - } - } - - refresh() { - console.log('refresh') - const hashes = this.layers.map((layer) => { - if (!layer.__updateMaterial) { - layer.__updateMaterial = this.refresh.bind(this) - } - return layer.getHash() - }) - const { uniforms, fragmentShader, vertexShader } = this.genShaders() - super.update({ fragmentShader, vertexShader, uniforms }) - } - - serialize(): Partial { - return { - constructor: 'LayerMaterial', - properties: { - color: this.color, - alpha: this.alpha, - name: this.name, - lighting: this.lighting, - }, - } - } - - set color(v: ColorRepresentation) { - if (this.uniforms?.u_lamina_color?.value) - this.uniforms.u_lamina_color.value = typeof v === 'string' ? new THREE.Color(v).convertSRGBToLinear() : v - } - get color() { - return this.uniforms?.u_lamina_color?.value - } - set alpha(v: number) { - this.uniforms.u_lamina_alpha.value = v - } - get alpha() { - return this.uniforms.u_lamina_alpha.value - } -} - -export { LayerMaterial, Abstract, Depth, Color, Noise, Fresnel, Gradient, Matcap, Texture, Displace, Normal, Shader } +export * from './core/LayerMaterial' +export * from './core/Loader' diff --git a/tsconfig.json b/tsconfig.json index 4f7c3bc..b1b302a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,8 +12,7 @@ "declaration": true, "declarationDir": "dist", "skipLibCheck": true, - "removeComments": false, - "baseUrl": "." + "removeComments": false }, "include": ["src/**/*"] } diff --git a/yarn.lock b/yarn.lock index 556610e..89931f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2394,9 +2394,9 @@ eastasianwidth@^0.2.0: integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== electron-to-chromium@^1.4.118: - version "1.4.127" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.127.tgz#4ef19d5d920abe2676d938f4170729b44f7f423a" - integrity sha512-nhD6S8nKI0O2MueC6blNOEZio+/PWppE/pevnf3LOlQA/fKPCrDp2Ao4wx4LFwmIkJpVdFdn2763YWLy9ENIZg== + version "1.4.129" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.129.tgz#c675793885721beefff99da50f57c6525c2cd238" + integrity sha512-GgtN6bsDtHdtXJtlMYZWGB/uOyjZWjmRDumXTas7dGBaB9zUyCjzHet1DY2KhyHN8R0GLbzZWqm4efeddqqyRQ== elliptic@^6.5.3: version "6.5.4" @@ -4180,9 +4180,9 @@ rollup-plugin-terser@^7.0.2: terser "^5.0.0" rollup@^2.70.2: - version "2.70.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.2.tgz#808d206a8851628a065097b7ba2053bd83ba0c0d" - integrity sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg== + version "2.71.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.71.1.tgz#82b259af7733dfd1224a8171013aaaad02971a22" + integrity sha512-lMZk3XfUBGjrrZQpvPSoXcZSfKcJ2Bgn+Z0L1MoW2V8Wh7BVM+LOBJTPo16yul2MwL59cXedzW1ruq3rCjSRgw== optionalDependencies: fsevents "~2.3.2"