|
|
import * as THREE from 'three'; |
|
|
import * as React from 'react'; |
|
|
import { shaderMaterial } from '../core/shaderMaterial.js'; |
|
|
|
|
|
const WireframeMaterialShaders = { |
|
|
uniforms: { |
|
|
strokeOpacity: 1, |
|
|
fillOpacity: 0.25, |
|
|
fillMix: 0, |
|
|
thickness: 0.05, |
|
|
colorBackfaces: false, |
|
|
dashInvert: true, |
|
|
dash: false, |
|
|
dashRepeats: 4, |
|
|
dashLength: 0.5, |
|
|
squeeze: false, |
|
|
squeezeMin: 0.2, |
|
|
squeezeMax: 1, |
|
|
stroke: new THREE.Color('#ff0000'), |
|
|
backfaceStroke: new THREE.Color('#0000ff'), |
|
|
fill: new THREE.Color('#00ff00') |
|
|
}, |
|
|
vertex: ` |
|
|
attribute vec3 barycentric; |
|
|
|
|
|
varying vec3 v_edges_Barycentric; |
|
|
varying vec3 v_edges_Position; |
|
|
|
|
|
void initWireframe() { |
|
|
v_edges_Barycentric = barycentric; |
|
|
v_edges_Position = position.xyz; |
|
|
} |
|
|
`, |
|
|
fragment: ` |
|
|
#ifndef PI |
|
|
#define PI 3.1415926535897932384626433832795 |
|
|
#endif |
|
|
|
|
|
varying vec3 v_edges_Barycentric; |
|
|
varying vec3 v_edges_Position; |
|
|
|
|
|
uniform float strokeOpacity; |
|
|
uniform float fillOpacity; |
|
|
uniform float fillMix; |
|
|
uniform float thickness; |
|
|
uniform bool colorBackfaces; |
|
|
|
|
|
// Dash |
|
|
uniform bool dashInvert; |
|
|
uniform bool dash; |
|
|
uniform bool dashOnly; |
|
|
uniform float dashRepeats; |
|
|
uniform float dashLength; |
|
|
|
|
|
// Squeeze |
|
|
uniform bool squeeze; |
|
|
uniform float squeezeMin; |
|
|
uniform float squeezeMax; |
|
|
|
|
|
// Colors |
|
|
uniform vec3 stroke; |
|
|
uniform vec3 backfaceStroke; |
|
|
uniform vec3 fill; |
|
|
|
|
|
// This is like |
|
|
float wireframe_aastep(float threshold, float dist) { |
|
|
float afwidth = fwidth(dist) * 0.5; |
|
|
return smoothstep(threshold - afwidth, threshold + afwidth, dist); |
|
|
} |
|
|
|
|
|
float wireframe_map(float value, float min1, float max1, float min2, float max2) { |
|
|
return min2 + (value - min1) * (max2 - min2) / (max1 - min1); |
|
|
} |
|
|
|
|
|
float getWireframe() { |
|
|
vec3 barycentric = v_edges_Barycentric; |
|
|
|
|
|
// Distance from center of each triangle to its edges. |
|
|
float d = min(min(barycentric.x, barycentric.y), barycentric.z); |
|
|
|
|
|
// for dashed rendering, we can use this to get the 0 .. 1 value of the line length |
|
|
float positionAlong = max(barycentric.x, barycentric.y); |
|
|
if (barycentric.y < barycentric.x && barycentric.y < barycentric.z) { |
|
|
positionAlong = 1.0 - positionAlong; |
|
|
} |
|
|
|
|
|
// the thickness of the stroke |
|
|
float computedThickness = wireframe_map(thickness, 0.0, 1.0, 0.0, 0.34); |
|
|
|
|
|
// if we want to shrink the thickness toward the center of the line segment |
|
|
if (squeeze) { |
|
|
computedThickness *= mix(squeezeMin, squeezeMax, (1.0 - sin(positionAlong * PI))); |
|
|
} |
|
|
|
|
|
// Create dash pattern |
|
|
if (dash) { |
|
|
// here we offset the stroke position depending on whether it |
|
|
// should overlap or not |
|
|
float offset = 1.0 / dashRepeats * dashLength / 2.0; |
|
|
if (!dashInvert) { |
|
|
offset += 1.0 / dashRepeats / 2.0; |
|
|
} |
|
|
|
|
|
// if we should animate the dash or not |
|
|
// if (dashAnimate) { |
|
|
// offset += time * 0.22; |
|
|
// } |
|
|
|
|
|
// create the repeating dash pattern |
|
|
float pattern = fract((positionAlong + offset) * dashRepeats); |
|
|
computedThickness *= 1.0 - wireframe_aastep(dashLength, pattern); |
|
|
} |
|
|
|
|
|
// compute the anti-aliased stroke edge |
|
|
float edge = 1.0 - wireframe_aastep(computedThickness, d); |
|
|
|
|
|
return edge; |
|
|
} |
|
|
` |
|
|
}; |
|
|
const WireframeMaterial = shaderMaterial(WireframeMaterialShaders.uniforms, WireframeMaterialShaders.vertex + ` |
|
|
void main() { |
|
|
initWireframe(); |
|
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); |
|
|
} |
|
|
`, WireframeMaterialShaders.fragment + ` |
|
|
void main () { |
|
|
// Compute color |
|
|
|
|
|
float edge = getWireframe(); |
|
|
vec4 colorStroke = vec4(stroke, edge); |
|
|
|
|
|
#ifdef FLIP_SIDED |
|
|
colorStroke.rgb = backfaceStroke; |
|
|
#endif |
|
|
|
|
|
vec4 colorFill = vec4(fill, fillOpacity); |
|
|
vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity); |
|
|
|
|
|
gl_FragColor = outColor; |
|
|
} |
|
|
`); |
|
|
function setWireframeOverride(material, uniforms) { |
|
|
material.onBeforeCompile = shader => { |
|
|
shader.uniforms = { |
|
|
...shader.uniforms, |
|
|
...uniforms |
|
|
}; |
|
|
shader.vertexShader = shader.vertexShader.replace('void main() {', ` |
|
|
${WireframeMaterialShaders.vertex} |
|
|
void main() { |
|
|
initWireframe(); |
|
|
`); |
|
|
shader.fragmentShader = shader.fragmentShader.replace('void main() {', ` |
|
|
${WireframeMaterialShaders.fragment} |
|
|
void main() { |
|
|
`); |
|
|
shader.fragmentShader = shader.fragmentShader.replace('#include <color_fragment>', ` |
|
|
#include <color_fragment> |
|
|
float edge = getWireframe(); |
|
|
vec4 colorStroke = vec4(stroke, edge); |
|
|
#ifdef FLIP_SIDED |
|
|
colorStroke.rgb = backfaceStroke; |
|
|
#endif |
|
|
vec4 colorFill = vec4(mix(diffuseColor.rgb, fill, fillMix), mix(diffuseColor.a, fillOpacity, fillMix)); |
|
|
vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity); |
|
|
|
|
|
diffuseColor.rgb = outColor.rgb; |
|
|
diffuseColor.a *= outColor.a; |
|
|
`); |
|
|
}; |
|
|
material.side = THREE.DoubleSide; |
|
|
material.transparent = true; |
|
|
} |
|
|
function useWireframeUniforms(uniforms, props) { |
|
|
React.useEffect(() => { |
|
|
var _props$fillOpacity; |
|
|
return void (uniforms.fillOpacity.value = (_props$fillOpacity = props.fillOpacity) !== null && _props$fillOpacity !== void 0 ? _props$fillOpacity : uniforms.fillOpacity.value); |
|
|
}, [props.fillOpacity]); |
|
|
React.useEffect(() => { |
|
|
var _props$fillMix; |
|
|
return void (uniforms.fillMix.value = (_props$fillMix = props.fillMix) !== null && _props$fillMix !== void 0 ? _props$fillMix : uniforms.fillMix.value); |
|
|
}, [props.fillMix]); |
|
|
React.useEffect(() => { |
|
|
var _props$strokeOpacity; |
|
|
return void (uniforms.strokeOpacity.value = (_props$strokeOpacity = props.strokeOpacity) !== null && _props$strokeOpacity !== void 0 ? _props$strokeOpacity : uniforms.strokeOpacity.value); |
|
|
}, [props.strokeOpacity]); |
|
|
React.useEffect(() => { |
|
|
var _props$thickness; |
|
|
return void (uniforms.thickness.value = (_props$thickness = props.thickness) !== null && _props$thickness !== void 0 ? _props$thickness : uniforms.thickness.value); |
|
|
}, [props.thickness]); |
|
|
React.useEffect(() => void (uniforms.colorBackfaces.value = !!props.colorBackfaces), [props.colorBackfaces]); |
|
|
React.useEffect(() => void (uniforms.dash.value = !!props.dash), [props.dash]); |
|
|
React.useEffect(() => void (uniforms.dashInvert.value = !!props.dashInvert), [props.dashInvert]); |
|
|
React.useEffect(() => { |
|
|
var _props$dashRepeats; |
|
|
return void (uniforms.dashRepeats.value = (_props$dashRepeats = props.dashRepeats) !== null && _props$dashRepeats !== void 0 ? _props$dashRepeats : uniforms.dashRepeats.value); |
|
|
}, [props.dashRepeats]); |
|
|
React.useEffect(() => { |
|
|
var _props$dashLength; |
|
|
return void (uniforms.dashLength.value = (_props$dashLength = props.dashLength) !== null && _props$dashLength !== void 0 ? _props$dashLength : uniforms.dashLength.value); |
|
|
}, [props.dashLength]); |
|
|
React.useEffect(() => void (uniforms.squeeze.value = !!props.squeeze), [props.squeeze]); |
|
|
React.useEffect(() => { |
|
|
var _props$squeezeMin; |
|
|
return void (uniforms.squeezeMin.value = (_props$squeezeMin = props.squeezeMin) !== null && _props$squeezeMin !== void 0 ? _props$squeezeMin : uniforms.squeezeMin.value); |
|
|
}, [props.squeezeMin]); |
|
|
React.useEffect(() => { |
|
|
var _props$squeezeMax; |
|
|
return void (uniforms.squeezeMax.value = (_props$squeezeMax = props.squeezeMax) !== null && _props$squeezeMax !== void 0 ? _props$squeezeMax : uniforms.squeezeMax.value); |
|
|
}, [props.squeezeMax]); |
|
|
React.useEffect(() => void (uniforms.stroke.value = props.stroke ? new THREE.Color(props.stroke) : uniforms.stroke.value), [props.stroke]); |
|
|
React.useEffect(() => void (uniforms.fill.value = props.fill ? new THREE.Color(props.fill) : uniforms.fill.value), [props.fill]); |
|
|
React.useEffect(() => void (uniforms.backfaceStroke.value = props.backfaceStroke ? new THREE.Color(props.backfaceStroke) : uniforms.backfaceStroke.value), [props.backfaceStroke]); |
|
|
} |
|
|
|
|
|
export { WireframeMaterial, WireframeMaterialShaders, setWireframeOverride, useWireframeUniforms }; |
|
|
|