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 0000000..e05927d Binary files /dev/null and b/examples/example-configurator/public/UV.jpg differ diff --git a/examples/example-configurator/src/Monkey.tsx b/examples/example-configurator/src/Monkey.tsx index 5ff465c..8cff0f0 100644 --- a/examples/example-configurator/src/Monkey.tsx +++ b/examples/example-configurator/src/Monkey.tsx @@ -2,15 +2,15 @@ Auto-generated by: https://github.com/pmndrs/gltfjsx */ -import React, { useEffect, useMemo, useState } from 'react' -import { useGLTF, TransformControls, OrbitControls } from '@react-three/drei' -import { Color, DebugLayerMaterial, Depth, LayerMaterial } from 'lamina' +import React, { useState } from 'react' +import { useGLTF, TransformControls, OrbitControls, useTexture } from '@react-three/drei' +import { DebugLayerMaterial } from 'lamina/debug' import * as LAYERS from 'lamina' import { button, useControls } from 'leva' -import { SerializedLayer } from 'lamina/types' +import { Texture } from 'lamina' export default function Monkey() { - const { nodes, scene } = useGLTF('/monkey.glb') as any + const { nodes } = useGLTF('/monkey.glb') as any const orbitControls = React.useRef(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"