mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 23:50:08 +00:00
[3d] add lineart mode (#2800)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
:hasBackgroundImage="hasBackgroundImage"
|
||||
:upDirection="upDirection"
|
||||
:materialMode="materialMode"
|
||||
:isAnimation="false"
|
||||
@updateBackgroundImage="handleBackgroundImageUpdate"
|
||||
@switchCamera="switchCamera"
|
||||
@toggleGrid="toggleGrid"
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
:hasBackgroundImage="hasBackgroundImage"
|
||||
:upDirection="upDirection"
|
||||
:materialMode="materialMode"
|
||||
:isAnimation="true"
|
||||
@updateBackgroundImage="handleBackgroundImageUpdate"
|
||||
@switchCamera="switchCamera"
|
||||
@toggleGrid="toggleGrid"
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
</div>
|
||||
|
||||
<div v-if="activeCategory === 'model'" class="flex flex-col">
|
||||
<div class="relative show-up-direction">
|
||||
<div v-if="notMaterialLineart" class="relative show-up-direction">
|
||||
<Button
|
||||
class="p-button-rounded p-button-text"
|
||||
@click="toggleUpDirection"
|
||||
@@ -278,6 +278,7 @@ const props = defineProps<{
|
||||
hasBackgroundImage?: boolean
|
||||
upDirection: UpDirection
|
||||
materialMode: MaterialMode
|
||||
isAnimation: boolean
|
||||
}>()
|
||||
|
||||
const isMenuOpen = ref(false)
|
||||
@@ -346,12 +347,25 @@ const upDirections: UpDirection[] = [
|
||||
'+z'
|
||||
]
|
||||
const showMaterialMode = ref(false)
|
||||
const materialModes: MaterialMode[] = [
|
||||
'original',
|
||||
'normal',
|
||||
'wireframe'
|
||||
//'depth' disable for now
|
||||
]
|
||||
|
||||
const materialModes = computed(() => {
|
||||
const modes: MaterialMode[] = [
|
||||
'original',
|
||||
'normal',
|
||||
'wireframe'
|
||||
//'depth' disable for now
|
||||
]
|
||||
|
||||
if (!props.isAnimation) {
|
||||
modes.push('lineart')
|
||||
}
|
||||
|
||||
return modes
|
||||
})
|
||||
|
||||
const notMaterialLineart = computed(() => {
|
||||
return props.materialMode !== 'lineart'
|
||||
})
|
||||
|
||||
const switchCamera = () => {
|
||||
emit('switchCamera')
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LGraphNode } from '@comfyorg/litegraph'
|
||||
import { onMounted, onUnmounted, ref, toRaw, watchEffect } from 'vue'
|
||||
import { onMounted, onUnmounted, ref, toRaw, watch, watchEffect } from 'vue'
|
||||
|
||||
import LoadingOverlay from '@/components/load3d/LoadingOverlay.vue'
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
MaterialMode,
|
||||
UpDirection
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
import { t } from '@/i18n'
|
||||
import { useLoad3dService } from '@/services/load3dService'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -50,8 +51,12 @@ const eventConfig = {
|
||||
backgroundImageChange: (value: string) =>
|
||||
emit('backgroundImageChange', value),
|
||||
upDirectionChange: (value: string) => emit('upDirectionChange', value),
|
||||
modelLoadingStart: () => loadingOverlayRef.value?.startLoading(),
|
||||
modelLoadingEnd: () => loadingOverlayRef.value?.endLoading()
|
||||
modelLoadingStart: () =>
|
||||
loadingOverlayRef.value?.startLoading(t('load3d.loadingModel')),
|
||||
modelLoadingEnd: () => loadingOverlayRef.value?.endLoading(),
|
||||
materialLoadingStart: () =>
|
||||
loadingOverlayRef.value?.startLoading(t('load3d.switchingMaterialMode')),
|
||||
materialLoadingEnd: () => loadingOverlayRef.value?.endLoading()
|
||||
} as const
|
||||
|
||||
watchEffect(() => {
|
||||
@@ -66,10 +71,20 @@ watchEffect(() => {
|
||||
rawLoad3d.togglePreview(props.showPreview)
|
||||
rawLoad3d.setBackgroundImage(props.backgroundImage)
|
||||
rawLoad3d.setUpDirection(props.upDirection)
|
||||
rawLoad3d.setMaterialMode(props.materialMode)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.materialMode,
|
||||
(newValue) => {
|
||||
if (load3d.value) {
|
||||
const rawLoad3d = toRaw(load3d.value)
|
||||
|
||||
rawLoad3d.setMaterialMode(newValue)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'materialModeChange', materialMode: string): void
|
||||
(e: 'backgroundColorChange', color: string): void
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="spinner"></div>
|
||||
<div class="text-white mt-4 text-lg">
|
||||
{{ t('load3d.loadingModel') }}
|
||||
{{ loadingMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,9 +20,11 @@ import { ref } from 'vue'
|
||||
import { t } from '@/i18n'
|
||||
|
||||
const modelLoading = ref(false)
|
||||
const loadingMessage = ref('')
|
||||
|
||||
const startLoading = () => {
|
||||
const startLoading = (message?: string) => {
|
||||
modelLoading.value = true
|
||||
loadingMessage.value = message || t('load3d.loadingModel')
|
||||
}
|
||||
|
||||
const endLoading = () => {
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import * as THREE from 'three'
|
||||
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
|
||||
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2'
|
||||
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry'
|
||||
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'
|
||||
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils'
|
||||
|
||||
import { ColoredShadowMaterial } from './conditional-lines/ColoredShadowMaterial'
|
||||
import { ConditionalEdgesGeometry } from './conditional-lines/ConditionalEdgesGeometry'
|
||||
import { ConditionalEdgesShader } from './conditional-lines/ConditionalEdgesShader.js'
|
||||
import { ConditionalLineMaterial } from './conditional-lines/Lines2/ConditionalLineMaterial'
|
||||
import { ConditionalLineSegmentsGeometry } from './conditional-lines/Lines2/ConditionalLineSegmentsGeometry'
|
||||
import {
|
||||
EventManagerInterface,
|
||||
MaterialMode,
|
||||
@@ -10,7 +19,12 @@ import {
|
||||
|
||||
export class ModelManager implements ModelManagerInterface {
|
||||
currentModel: THREE.Object3D | null = null
|
||||
originalModel: THREE.Object3D | THREE.BufferGeometry | GLTF | null = null
|
||||
originalModel:
|
||||
| THREE.Object3D
|
||||
| THREE.Group
|
||||
| THREE.BufferGeometry
|
||||
| GLTF
|
||||
| null = null
|
||||
originalRotation: THREE.Euler | null = null
|
||||
currentUpDirection: UpDirection = 'original'
|
||||
materialMode: MaterialMode = 'original'
|
||||
@@ -27,6 +41,15 @@ export class ModelManager implements ModelManagerInterface {
|
||||
private activeCamera: THREE.Camera
|
||||
private setupCamera: (size: THREE.Vector3) => void
|
||||
|
||||
LIGHT_MODEL = 0xffffff
|
||||
LIGHT_LINES = 0x455a64
|
||||
|
||||
conditionalModel: THREE.Object3D | null = null
|
||||
edgesModel: THREE.Object3D | null = null
|
||||
backgroundModel: THREE.Object3D | null = null
|
||||
shadowModel: THREE.Object3D | null = null
|
||||
depthModel: THREE.Object3D | null = null
|
||||
|
||||
constructor(
|
||||
scene: THREE.Scene,
|
||||
renderer: THREE.WebGLRenderer,
|
||||
@@ -71,6 +94,8 @@ export class ModelManager implements ModelManagerInterface {
|
||||
this.standardMaterial.dispose()
|
||||
this.wireframeMaterial.dispose()
|
||||
this.depthMaterial.dispose()
|
||||
|
||||
this.disposeLineartModel()
|
||||
}
|
||||
|
||||
createSTLMaterial(): THREE.MeshStandardMaterial {
|
||||
@@ -83,10 +108,271 @@ export class ModelManager implements ModelManagerInterface {
|
||||
})
|
||||
}
|
||||
|
||||
disposeLineartModel(): void {
|
||||
this.disposeEdgesModel()
|
||||
this.disposeShadowModel()
|
||||
this.disposeBackgroundModel()
|
||||
this.disposeDepthModel()
|
||||
this.disposeConditionalModel()
|
||||
}
|
||||
|
||||
disposeEdgesModel(): void {
|
||||
if (this.edgesModel) {
|
||||
if (this.edgesModel.parent) {
|
||||
this.edgesModel.parent.remove(this.edgesModel)
|
||||
}
|
||||
|
||||
this.edgesModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
if (Array.isArray(child.material)) {
|
||||
child.material.forEach((m) => m.dispose())
|
||||
} else {
|
||||
child.material.dispose()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
initEdgesModel() {
|
||||
this.disposeEdgesModel()
|
||||
|
||||
if (!this.currentModel) {
|
||||
return
|
||||
}
|
||||
|
||||
this.edgesModel = this.currentModel.clone()
|
||||
this.scene.add(this.edgesModel)
|
||||
|
||||
const meshes: THREE.Mesh[] = []
|
||||
|
||||
this.edgesModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
meshes.push(child)
|
||||
}
|
||||
})
|
||||
|
||||
for (const key in meshes) {
|
||||
const mesh = meshes[key]
|
||||
const parent = mesh.parent
|
||||
|
||||
let lineGeom = new THREE.EdgesGeometry(mesh.geometry, 10)
|
||||
|
||||
const line = new THREE.LineSegments(
|
||||
lineGeom,
|
||||
new THREE.LineBasicMaterial({ color: this.LIGHT_LINES })
|
||||
)
|
||||
line.position.copy(mesh.position)
|
||||
line.scale.copy(mesh.scale)
|
||||
line.rotation.copy(mesh.rotation)
|
||||
|
||||
const thickLineGeom = new LineSegmentsGeometry().fromEdgesGeometry(
|
||||
lineGeom
|
||||
)
|
||||
const thickLines = new LineSegments2(
|
||||
thickLineGeom,
|
||||
new LineMaterial({ color: this.LIGHT_LINES, linewidth: 13 })
|
||||
)
|
||||
thickLines.position.copy(mesh.position)
|
||||
thickLines.scale.copy(mesh.scale)
|
||||
thickLines.rotation.copy(mesh.rotation)
|
||||
|
||||
parent?.remove(mesh)
|
||||
parent?.add(line)
|
||||
parent?.add(thickLines)
|
||||
}
|
||||
}
|
||||
|
||||
disposeBackgroundModel(): void {
|
||||
if (this.backgroundModel) {
|
||||
if (this.backgroundModel.parent) {
|
||||
this.backgroundModel.parent.remove(this.backgroundModel)
|
||||
}
|
||||
|
||||
this.backgroundModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.material.dispose()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
disposeShadowModel(): void {
|
||||
if (this.shadowModel) {
|
||||
if (this.shadowModel.parent) {
|
||||
this.shadowModel.parent.remove(this.shadowModel)
|
||||
}
|
||||
|
||||
this.shadowModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.material.dispose()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
disposeDepthModel(): void {
|
||||
if (this.depthModel) {
|
||||
if (this.depthModel.parent) {
|
||||
this.depthModel.parent.remove(this.depthModel)
|
||||
}
|
||||
|
||||
this.depthModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.material.dispose()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
disposeConditionalModel(): void {
|
||||
if (this.conditionalModel) {
|
||||
if (this.conditionalModel.parent) {
|
||||
this.conditionalModel.parent.remove(this.conditionalModel)
|
||||
}
|
||||
|
||||
this.conditionalModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.material.dispose()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
initBackgroundModel() {
|
||||
this.disposeBackgroundModel()
|
||||
this.disposeShadowModel()
|
||||
this.disposeDepthModel()
|
||||
|
||||
if (!this.currentModel) {
|
||||
return
|
||||
}
|
||||
|
||||
this.backgroundModel = this.currentModel.clone()
|
||||
this.backgroundModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.material = new THREE.MeshBasicMaterial({
|
||||
color: this.LIGHT_MODEL
|
||||
})
|
||||
child.material.polygonOffset = true
|
||||
child.material.polygonOffsetFactor = 1
|
||||
child.material.polygonOffsetUnits = 1
|
||||
child.renderOrder = 2
|
||||
}
|
||||
})
|
||||
|
||||
this.scene.add(this.backgroundModel)
|
||||
|
||||
this.shadowModel = this.currentModel.clone()
|
||||
this.shadowModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.material = new ColoredShadowMaterial({
|
||||
color: this.LIGHT_MODEL,
|
||||
shininess: 1.0
|
||||
})
|
||||
child.material.polygonOffset = true
|
||||
child.material.polygonOffsetFactor = 1
|
||||
child.material.polygonOffsetUnits = 1
|
||||
child.receiveShadow = true
|
||||
child.renderOrder = 2
|
||||
}
|
||||
})
|
||||
|
||||
this.scene.add(this.shadowModel)
|
||||
|
||||
this.depthModel = this.currentModel.clone()
|
||||
|
||||
this.depthModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.material = new THREE.MeshBasicMaterial({
|
||||
color: this.LIGHT_MODEL
|
||||
})
|
||||
child.material.polygonOffset = true
|
||||
child.material.polygonOffsetFactor = 1
|
||||
child.material.polygonOffsetUnits = 1
|
||||
child.material.colorWrite = false
|
||||
child.renderOrder = 1
|
||||
}
|
||||
})
|
||||
|
||||
this.scene.add(this.depthModel)
|
||||
}
|
||||
|
||||
initConditionalModel() {
|
||||
this.disposeConditionalModel()
|
||||
|
||||
if (!this.currentModel) {
|
||||
return
|
||||
}
|
||||
|
||||
this.conditionalModel = this.currentModel.clone()
|
||||
this.scene.add(this.conditionalModel)
|
||||
this.conditionalModel.visible = true
|
||||
|
||||
const meshes: THREE.Mesh[] = []
|
||||
|
||||
this.conditionalModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
meshes.push(child)
|
||||
}
|
||||
})
|
||||
|
||||
for (const key in meshes) {
|
||||
const mesh = meshes[key]
|
||||
const parent = mesh.parent
|
||||
|
||||
const mergedGeom = mesh.geometry.clone()
|
||||
for (const key in mergedGeom.attributes) {
|
||||
if (key !== 'position') {
|
||||
mergedGeom.deleteAttribute(key)
|
||||
}
|
||||
}
|
||||
|
||||
const lineGeom = new ConditionalEdgesGeometry(mergeVertices(mergedGeom))
|
||||
const material = new THREE.ShaderMaterial(ConditionalEdgesShader)
|
||||
material.uniforms.diffuse.value.set(this.LIGHT_LINES)
|
||||
|
||||
const line = new THREE.LineSegments(lineGeom, material)
|
||||
line.position.copy(mesh.position)
|
||||
line.scale.copy(mesh.scale)
|
||||
line.rotation.copy(mesh.rotation)
|
||||
|
||||
const thickLineGeom =
|
||||
new ConditionalLineSegmentsGeometry().fromConditionalEdgesGeometry(
|
||||
lineGeom
|
||||
)
|
||||
|
||||
const conditionalLineMaterial = new ConditionalLineMaterial({
|
||||
color: this.LIGHT_LINES,
|
||||
linewidth: 2
|
||||
})
|
||||
|
||||
const thickLines = new LineSegments2(
|
||||
thickLineGeom,
|
||||
conditionalLineMaterial
|
||||
)
|
||||
thickLines.position.copy(mesh.position)
|
||||
thickLines.scale.copy(mesh.scale)
|
||||
thickLines.rotation.copy(mesh.rotation)
|
||||
|
||||
parent?.remove(mesh)
|
||||
parent?.add(line)
|
||||
parent?.add(thickLines)
|
||||
}
|
||||
}
|
||||
|
||||
setMaterialMode(mode: MaterialMode): void {
|
||||
if (!this.currentModel || mode === this.materialMode) {
|
||||
return
|
||||
}
|
||||
|
||||
this.disposeLineartModel()
|
||||
|
||||
this.materialMode = mode
|
||||
|
||||
if (!this.currentModel) return
|
||||
if (mode === 'lineart' || this.materialMode === 'lineart') {
|
||||
this.eventManager.emitEvent('materialLoadingStart', null)
|
||||
}
|
||||
|
||||
if (mode === 'depth') {
|
||||
this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace
|
||||
@@ -94,6 +380,10 @@ export class ModelManager implements ModelManagerInterface {
|
||||
this.renderer.outputColorSpace = THREE.SRGBColorSpace
|
||||
}
|
||||
|
||||
if (this.currentModel) {
|
||||
this.currentModel.visible = mode !== 'lineart'
|
||||
}
|
||||
|
||||
this.currentModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
switch (mode) {
|
||||
@@ -153,7 +443,6 @@ export class ModelManager implements ModelManagerInterface {
|
||||
opacity: 1.0
|
||||
})
|
||||
break
|
||||
|
||||
case 'wireframe':
|
||||
if (!this.originalMaterials.has(child)) {
|
||||
this.originalMaterials.set(child, child.material)
|
||||
@@ -165,7 +454,6 @@ export class ModelManager implements ModelManagerInterface {
|
||||
opacity: 1.0
|
||||
})
|
||||
break
|
||||
|
||||
case 'original':
|
||||
const originalMaterial = this.originalMaterials.get(child)
|
||||
if (originalMaterial) {
|
||||
@@ -178,6 +466,57 @@ export class ModelManager implements ModelManagerInterface {
|
||||
}
|
||||
})
|
||||
|
||||
if (mode === 'lineart') {
|
||||
setTimeout(() => {
|
||||
this.initEdgesModel()
|
||||
this.initBackgroundModel()
|
||||
this.initConditionalModel()
|
||||
|
||||
if (this.conditionalModel) {
|
||||
this.conditionalModel.traverse((child) => {
|
||||
if (
|
||||
child instanceof THREE.Mesh &&
|
||||
child.material &&
|
||||
child.material.resolution
|
||||
) {
|
||||
this.renderer.getSize(child.material.resolution)
|
||||
child.material.resolution.multiplyScalar(window.devicePixelRatio)
|
||||
child.material.linewidth = 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (this.edgesModel) {
|
||||
this.edgesModel.traverse((child) => {
|
||||
if (
|
||||
child instanceof THREE.Mesh &&
|
||||
child.material &&
|
||||
child.material.resolution
|
||||
) {
|
||||
this.renderer.getSize(child.material.resolution)
|
||||
child.material.resolution.multiplyScalar(window.devicePixelRatio)
|
||||
child.material.linewidth = 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (this.backgroundModel) {
|
||||
this.backgroundModel.visible = true
|
||||
this.backgroundModel.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh && child.material) {
|
||||
child.material.transparent = false
|
||||
child.material.opacity = 0.25
|
||||
child.material.color.set(this.LIGHT_MODEL)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.eventManager.emitEvent('materialLoadingEnd', null)
|
||||
}, 50)
|
||||
} else if (this.materialMode === 'lineart') {
|
||||
this.eventManager.emitEvent('materialLoadingEnd', null)
|
||||
}
|
||||
|
||||
this.eventManager.emitEvent('materialModeChange', mode)
|
||||
}
|
||||
|
||||
@@ -188,9 +527,7 @@ export class ModelManager implements ModelManagerInterface {
|
||||
}
|
||||
})
|
||||
|
||||
if (this.materialMode !== 'original') {
|
||||
this.setMaterialMode(this.materialMode)
|
||||
}
|
||||
this.setMaterialMode('original')
|
||||
}
|
||||
|
||||
clearModel(): void {
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { Color, ShaderLib, ShaderMaterial, UniformsUtils } from 'three'
|
||||
|
||||
export class ColoredShadowMaterial extends ShaderMaterial {
|
||||
get color() {
|
||||
return this.uniforms.diffuse.value
|
||||
}
|
||||
|
||||
get shadowColor() {
|
||||
return this.uniforms.shadowColor.value
|
||||
}
|
||||
|
||||
set shininess(v) {
|
||||
this.uniforms.shininess.value = v
|
||||
}
|
||||
get shininess() {
|
||||
return this.uniforms.shininess.value
|
||||
}
|
||||
|
||||
constructor(options) {
|
||||
super({
|
||||
uniforms: UniformsUtils.merge([
|
||||
ShaderLib.phong.uniforms,
|
||||
{
|
||||
shadowColor: {
|
||||
value: new Color(0xff0000)
|
||||
}
|
||||
}
|
||||
]),
|
||||
vertexShader: `
|
||||
#define PHONG
|
||||
varying vec3 vViewPosition;
|
||||
#ifndef FLAT_SHADED
|
||||
varying vec3 vNormal;
|
||||
#endif
|
||||
#include <common>
|
||||
#include <uv_pars_vertex>
|
||||
#include <uv2_pars_vertex>
|
||||
#include <displacementmap_pars_vertex>
|
||||
#include <envmap_pars_vertex>
|
||||
#include <color_pars_vertex>
|
||||
#include <fog_pars_vertex>
|
||||
#include <morphtarget_pars_vertex>
|
||||
#include <skinning_pars_vertex>
|
||||
#include <shadowmap_pars_vertex>
|
||||
#include <logdepthbuf_pars_vertex>
|
||||
#include <clipping_planes_pars_vertex>
|
||||
void main() {
|
||||
#include <uv_vertex>
|
||||
#include <uv2_vertex>
|
||||
#include <color_vertex>
|
||||
#include <beginnormal_vertex>
|
||||
#include <morphnormal_vertex>
|
||||
#include <skinbase_vertex>
|
||||
#include <skinnormal_vertex>
|
||||
#include <defaultnormal_vertex>
|
||||
#ifndef FLAT_SHADED
|
||||
vNormal = normalize( transformedNormal );
|
||||
#endif
|
||||
#include <begin_vertex>
|
||||
#include <morphtarget_vertex>
|
||||
#include <skinning_vertex>
|
||||
#include <displacementmap_vertex>
|
||||
#include <project_vertex>
|
||||
#include <logdepthbuf_vertex>
|
||||
#include <clipping_planes_vertex>
|
||||
vViewPosition = - mvPosition.xyz;
|
||||
#include <worldpos_vertex>
|
||||
#include <envmap_vertex>
|
||||
#include <shadowmap_vertex>
|
||||
#include <fog_vertex>
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
#define PHONG
|
||||
uniform vec3 diffuse;
|
||||
uniform vec3 emissive;
|
||||
uniform vec3 specular;
|
||||
uniform float shininess;
|
||||
uniform float opacity;
|
||||
uniform vec3 shadowColor;
|
||||
#include <common>
|
||||
#include <packing>
|
||||
#include <dithering_pars_fragment>
|
||||
#include <color_pars_fragment>
|
||||
#include <uv_pars_fragment>
|
||||
#include <uv2_pars_fragment>
|
||||
#include <map_pars_fragment>
|
||||
#include <alphamap_pars_fragment>
|
||||
#include <aomap_pars_fragment>
|
||||
#include <lightmap_pars_fragment>
|
||||
#include <emissivemap_pars_fragment>
|
||||
#include <envmap_common_pars_fragment>
|
||||
#include <envmap_pars_fragment>
|
||||
#include <cube_uv_reflection_fragment>
|
||||
#include <fog_pars_fragment>
|
||||
#include <bsdfs>
|
||||
#include <lights_pars_begin>
|
||||
#include <lights_phong_pars_fragment>
|
||||
#include <shadowmap_pars_fragment>
|
||||
#include <bumpmap_pars_fragment>
|
||||
#include <normalmap_pars_fragment>
|
||||
#include <specularmap_pars_fragment>
|
||||
#include <logdepthbuf_pars_fragment>
|
||||
#include <clipping_planes_pars_fragment>
|
||||
void main() {
|
||||
#include <clipping_planes_fragment>
|
||||
vec4 diffuseColor = vec4( 1.0, 1.0, 1.0, opacity );
|
||||
ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
|
||||
vec3 totalEmissiveRadiance = emissive;
|
||||
#include <logdepthbuf_fragment>
|
||||
#include <map_fragment>
|
||||
#include <color_fragment>
|
||||
#include <alphamap_fragment>
|
||||
#include <alphatest_fragment>
|
||||
#include <specularmap_fragment>
|
||||
#include <normal_fragment_begin>
|
||||
#include <normal_fragment_maps>
|
||||
#include <emissivemap_fragment>
|
||||
#include <lights_phong_fragment>
|
||||
#include <lights_fragment_begin>
|
||||
#include <lights_fragment_maps>
|
||||
#include <lights_fragment_end>
|
||||
#include <aomap_fragment>
|
||||
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;
|
||||
#include <envmap_fragment>
|
||||
|
||||
gl_FragColor = vec4( outgoingLight, diffuseColor.a );
|
||||
#include <tonemapping_fragment>
|
||||
#include <fog_fragment>
|
||||
#include <premultiplied_alpha_fragment>
|
||||
#include <dithering_fragment>
|
||||
|
||||
gl_FragColor.rgb = mix(
|
||||
shadowColor.rgb,
|
||||
diffuse.rgb,
|
||||
min( gl_FragColor.r, 1.0 )
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
`
|
||||
})
|
||||
|
||||
Object.defineProperties(this, {
|
||||
opacity: {
|
||||
set(v) {
|
||||
this.uniforms.opacity.value = v
|
||||
},
|
||||
|
||||
get() {
|
||||
return this.uniforms.opacity.value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.setValues(options)
|
||||
this.lights = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { BufferAttribute, BufferGeometry, Triangle, Vector3 } from 'three'
|
||||
|
||||
const vec0 = new Vector3()
|
||||
const vec1 = new Vector3()
|
||||
const vec2 = new Vector3()
|
||||
const vec3 = new Vector3()
|
||||
const vec4 = new Vector3()
|
||||
|
||||
const triangle0 = new Triangle()
|
||||
const triangle1 = new Triangle()
|
||||
const normal0 = new Vector3()
|
||||
const normal1 = new Vector3()
|
||||
export class ConditionalEdgesGeometry extends BufferGeometry {
|
||||
constructor(geometry) {
|
||||
super()
|
||||
|
||||
const edgeInfo = {}
|
||||
|
||||
const position = geometry.attributes.position
|
||||
let index
|
||||
if (geometry.index) {
|
||||
index = geometry.index
|
||||
} else {
|
||||
const arr = new Array(position.count / 3).fill().map((_, i) => i)
|
||||
index = new BufferAttribute(new Uint32Array(arr), 1, false)
|
||||
}
|
||||
|
||||
for (let i = 0, l = index.count; i < l; i += 3) {
|
||||
const indices = [index.getX(i + 0), index.getX(i + 1), index.getX(i + 2)]
|
||||
|
||||
for (let j = 0; j < 3; j++) {
|
||||
const index0 = indices[j]
|
||||
const index1 = indices[(j + 1) % 3]
|
||||
|
||||
const hash = `${index0}_${index1}`
|
||||
const reverseHash = `${index1}_${index0}`
|
||||
if (reverseHash in edgeInfo) {
|
||||
edgeInfo[reverseHash].controlIndex1 = indices[(j + 2) % 3]
|
||||
edgeInfo[reverseHash].tri1 = i / 3
|
||||
} else {
|
||||
edgeInfo[hash] = {
|
||||
index0,
|
||||
index1,
|
||||
|
||||
controlIndex0: indices[(j + 2) % 3],
|
||||
controlIndex1: null,
|
||||
|
||||
tri0: i / 3,
|
||||
tri1: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const edgePositions = []
|
||||
const edgeDirections = []
|
||||
const edgeControl0 = []
|
||||
const edgeControl1 = []
|
||||
for (const key in edgeInfo) {
|
||||
const { index0, index1, controlIndex0, controlIndex1, tri0, tri1 } =
|
||||
edgeInfo[key]
|
||||
|
||||
if (controlIndex1 === null) {
|
||||
continue
|
||||
}
|
||||
|
||||
triangle0.a.fromBufferAttribute(position, index.getX(tri0 * 3 + 0))
|
||||
triangle0.b.fromBufferAttribute(position, index.getX(tri0 * 3 + 1))
|
||||
triangle0.c.fromBufferAttribute(position, index.getX(tri0 * 3 + 2))
|
||||
|
||||
triangle1.a.fromBufferAttribute(position, index.getX(tri1 * 3 + 0))
|
||||
triangle1.b.fromBufferAttribute(position, index.getX(tri1 * 3 + 1))
|
||||
triangle1.c.fromBufferAttribute(position, index.getX(tri1 * 3 + 2))
|
||||
|
||||
triangle0.getNormal(normal0).normalize()
|
||||
triangle1.getNormal(normal1).normalize()
|
||||
|
||||
if (normal0.dot(normal1) < 0.01) {
|
||||
continue
|
||||
}
|
||||
|
||||
// positions
|
||||
vec0.fromBufferAttribute(position, index0)
|
||||
vec1.fromBufferAttribute(position, index1)
|
||||
|
||||
// direction
|
||||
vec2.subVectors(vec0, vec1)
|
||||
|
||||
// control positions
|
||||
vec3.fromBufferAttribute(position, controlIndex0)
|
||||
vec4.fromBufferAttribute(position, controlIndex1)
|
||||
|
||||
// create arrays
|
||||
edgePositions.push(vec0.x, vec0.y, vec0.z)
|
||||
edgeDirections.push(vec2.x, vec2.y, vec2.z)
|
||||
edgeControl0.push(vec3.x, vec3.y, vec3.z)
|
||||
edgeControl1.push(vec4.x, vec4.y, vec4.z)
|
||||
|
||||
edgePositions.push(vec1.x, vec1.y, vec1.z)
|
||||
edgeDirections.push(vec2.x, vec2.y, vec2.z)
|
||||
edgeControl0.push(vec3.x, vec3.y, vec3.z)
|
||||
edgeControl1.push(vec4.x, vec4.y, vec4.z)
|
||||
}
|
||||
|
||||
this.setAttribute(
|
||||
'position',
|
||||
new BufferAttribute(new Float32Array(edgePositions), 3, false)
|
||||
)
|
||||
this.setAttribute(
|
||||
'direction',
|
||||
new BufferAttribute(new Float32Array(edgeDirections), 3, false)
|
||||
)
|
||||
this.setAttribute(
|
||||
'control0',
|
||||
new BufferAttribute(new Float32Array(edgeControl0), 3, false)
|
||||
)
|
||||
this.setAttribute(
|
||||
'control1',
|
||||
new BufferAttribute(new Float32Array(edgeControl1), 3, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Color } from 'three'
|
||||
|
||||
export const ConditionalEdgesShader = {
|
||||
uniforms: {
|
||||
diffuse: {
|
||||
value: new Color()
|
||||
},
|
||||
|
||||
opacity: {
|
||||
value: 1.0
|
||||
}
|
||||
},
|
||||
|
||||
vertexShader: /* glsl */ `
|
||||
attribute vec3 control0;
|
||||
attribute vec3 control1;
|
||||
attribute vec3 direction;
|
||||
|
||||
#include <common>
|
||||
#include <color_pars_vertex>
|
||||
#include <fog_pars_vertex>
|
||||
#include <logdepthbuf_pars_vertex>
|
||||
#include <clipping_planes_pars_vertex>
|
||||
void main() {
|
||||
|
||||
#include <color_vertex>
|
||||
|
||||
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
|
||||
// Transform the line segment ends and control points into camera clip space
|
||||
vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0, 1.0 );
|
||||
vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1, 1.0 );
|
||||
vec4 p0 = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||
vec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + direction, 1.0 );
|
||||
|
||||
c0 /= c0.w;
|
||||
c1 /= c1.w;
|
||||
p0 /= p0.w;
|
||||
p1 /= p1.w;
|
||||
|
||||
// Get the direction of the segment and an orthogonal vector
|
||||
vec2 dir = p1.xy - p0.xy;
|
||||
vec2 norm = vec2( -dir.y, dir.x );
|
||||
|
||||
// Get control point directions from the line
|
||||
vec2 c0dir = c0.xy - p1.xy;
|
||||
vec2 c1dir = c1.xy - p1.xy;
|
||||
|
||||
// If the vectors to the controls points are pointed in different directions away
|
||||
// from the line segment then the line should not be drawn.
|
||||
float d0 = dot( normalize( norm ), normalize( c0dir ) );
|
||||
float d1 = dot( normalize( norm ), normalize( c1dir ) );
|
||||
float discardFlag = float( sign( d0 ) != sign( d1 ) );
|
||||
gl_Position = discardFlag > 0.5 ? c0 : gl_Position;
|
||||
|
||||
#include <logdepthbuf_vertex>
|
||||
#include <clipping_planes_vertex>
|
||||
#include <fog_vertex>
|
||||
|
||||
}
|
||||
`,
|
||||
|
||||
fragmentShader: /* glsl */ `
|
||||
uniform vec3 diffuse;
|
||||
uniform float opacity;
|
||||
|
||||
#include <common>
|
||||
#include <color_pars_fragment>
|
||||
#include <fog_pars_fragment>
|
||||
#include <logdepthbuf_pars_fragment>
|
||||
#include <clipping_planes_pars_fragment>
|
||||
void main() {
|
||||
|
||||
#include <clipping_planes_fragment>
|
||||
|
||||
vec3 outgoingLight = vec3( 0.0 );
|
||||
vec4 diffuseColor = vec4( diffuse, opacity );
|
||||
|
||||
#include <logdepthbuf_fragment>
|
||||
#include <color_fragment>
|
||||
|
||||
outgoingLight = diffuseColor.rgb; // simple shader
|
||||
gl_FragColor = vec4( outgoingLight, diffuseColor.a );
|
||||
|
||||
#include <tonemapping_fragment>
|
||||
#include <fog_fragment>
|
||||
#include <premultiplied_alpha_fragment>
|
||||
|
||||
}
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
import { ShaderMaterial, UniformsLib, UniformsUtils, Vector2 } from 'three'
|
||||
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
|
||||
|
||||
/**
|
||||
* parameters = {
|
||||
* color: <hex>,
|
||||
* linewidth: <float>,
|
||||
* dashed: <boolean>,
|
||||
* dashScale: <float>,
|
||||
* dashSize: <float>,
|
||||
* gapSize: <float>,
|
||||
* resolution: <Vector2>, // to be set by renderer
|
||||
* }
|
||||
*/
|
||||
|
||||
const uniforms = {
|
||||
linewidth: { value: 1 },
|
||||
resolution: { value: new Vector2(1, 1) },
|
||||
dashScale: { value: 1 },
|
||||
dashSize: { value: 1 },
|
||||
gapSize: { value: 1 }, // todo FIX - maybe change to totalSize
|
||||
opacity: { value: 1 }
|
||||
}
|
||||
|
||||
const shader = {
|
||||
uniforms: UniformsUtils.merge([
|
||||
UniformsLib.common,
|
||||
UniformsLib.fog,
|
||||
uniforms
|
||||
]),
|
||||
|
||||
vertexShader: /* glsl */ `
|
||||
#include <common>
|
||||
#include <color_pars_vertex>
|
||||
#include <fog_pars_vertex>
|
||||
#include <logdepthbuf_pars_vertex>
|
||||
#include <clipping_planes_pars_vertex>
|
||||
|
||||
uniform float linewidth;
|
||||
uniform vec2 resolution;
|
||||
|
||||
attribute vec3 control0;
|
||||
attribute vec3 control1;
|
||||
attribute vec3 direction;
|
||||
|
||||
attribute vec3 instanceStart;
|
||||
attribute vec3 instanceEnd;
|
||||
|
||||
attribute vec3 instanceColorStart;
|
||||
attribute vec3 instanceColorEnd;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
#ifdef USE_DASH
|
||||
|
||||
uniform float dashScale;
|
||||
attribute float instanceDistanceStart;
|
||||
attribute float instanceDistanceEnd;
|
||||
varying float vLineDistance;
|
||||
|
||||
#endif
|
||||
|
||||
void trimSegment( const in vec4 start, inout vec4 end ) {
|
||||
|
||||
// trim end segment so it terminates between the camera plane and the near plane
|
||||
|
||||
// conservative estimate of the near plane
|
||||
float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
|
||||
float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
|
||||
float nearEstimate = - 0.5 * b / a;
|
||||
|
||||
float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
|
||||
|
||||
end.xyz = mix( start.xyz, end.xyz, alpha );
|
||||
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
#ifdef USE_COLOR
|
||||
|
||||
vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USE_DASH
|
||||
|
||||
vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd;
|
||||
|
||||
#endif
|
||||
|
||||
float aspect = resolution.x / resolution.y;
|
||||
|
||||
vUv = uv;
|
||||
|
||||
// camera space
|
||||
vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
|
||||
vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
|
||||
|
||||
// special case for perspective projection, and segments that terminate either in, or behind, the camera plane
|
||||
// clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
|
||||
// but we need to perform ndc-space calculations in the shader, so we must address this issue directly
|
||||
// perhaps there is a more elegant solution -- WestLangley
|
||||
|
||||
bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
|
||||
|
||||
if ( perspective ) {
|
||||
|
||||
if ( start.z < 0.0 && end.z >= 0.0 ) {
|
||||
|
||||
trimSegment( start, end );
|
||||
|
||||
} else if ( end.z < 0.0 && start.z >= 0.0 ) {
|
||||
|
||||
trimSegment( end, start );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// clip space
|
||||
vec4 clipStart = projectionMatrix * start;
|
||||
vec4 clipEnd = projectionMatrix * end;
|
||||
|
||||
// ndc space
|
||||
vec2 ndcStart = clipStart.xy / clipStart.w;
|
||||
vec2 ndcEnd = clipEnd.xy / clipEnd.w;
|
||||
|
||||
// direction
|
||||
vec2 dir = ndcEnd - ndcStart;
|
||||
|
||||
// account for clip-space aspect ratio
|
||||
dir.x *= aspect;
|
||||
dir = normalize( dir );
|
||||
|
||||
// perpendicular to dir
|
||||
vec2 offset = vec2( dir.y, - dir.x );
|
||||
|
||||
// undo aspect ratio adjustment
|
||||
dir.x /= aspect;
|
||||
offset.x /= aspect;
|
||||
|
||||
// sign flip
|
||||
if ( position.x < 0.0 ) offset *= - 1.0;
|
||||
|
||||
// endcaps
|
||||
if ( position.y < 0.0 ) {
|
||||
|
||||
offset += - dir;
|
||||
|
||||
} else if ( position.y > 1.0 ) {
|
||||
|
||||
offset += dir;
|
||||
|
||||
}
|
||||
|
||||
// adjust for linewidth
|
||||
offset *= linewidth;
|
||||
|
||||
// adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
|
||||
offset /= resolution.y;
|
||||
|
||||
// select end
|
||||
vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
|
||||
|
||||
// back to clip space
|
||||
offset *= clip.w;
|
||||
|
||||
clip.xy += offset;
|
||||
|
||||
gl_Position = clip;
|
||||
|
||||
vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation
|
||||
|
||||
#include <logdepthbuf_vertex>
|
||||
#include <clipping_planes_vertex>
|
||||
#include <fog_vertex>
|
||||
|
||||
// conditional logic
|
||||
// Transform the line segment ends and control points into camera clip space
|
||||
vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0, 1.0 );
|
||||
vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1, 1.0 );
|
||||
vec4 p0 = projectionMatrix * modelViewMatrix * vec4( instanceStart, 1.0 );
|
||||
vec4 p1 = projectionMatrix * modelViewMatrix * vec4( instanceStart + direction, 1.0 );
|
||||
|
||||
c0 /= c0.w;
|
||||
c1 /= c1.w;
|
||||
p0 /= p0.w;
|
||||
p1 /= p1.w;
|
||||
|
||||
// Get the direction of the segment and an orthogonal vector
|
||||
vec2 segDir = p1.xy - p0.xy;
|
||||
vec2 norm = vec2( - segDir.y, segDir.x );
|
||||
|
||||
// Get control point directions from the line
|
||||
vec2 c0dir = c0.xy - p1.xy;
|
||||
vec2 c1dir = c1.xy - p1.xy;
|
||||
|
||||
// If the vectors to the controls points are pointed in different directions away
|
||||
// from the line segment then the line should not be drawn.
|
||||
float d0 = dot( normalize( norm ), normalize( c0dir ) );
|
||||
float d1 = dot( normalize( norm ), normalize( c1dir ) );
|
||||
float discardFlag = float( sign( d0 ) != sign( d1 ) );
|
||||
gl_Position = discardFlag > 0.5 ? c0 : gl_Position;
|
||||
// end conditional line logic
|
||||
|
||||
}
|
||||
`,
|
||||
|
||||
fragmentShader: /* glsl */ `
|
||||
uniform vec3 diffuse;
|
||||
uniform float opacity;
|
||||
|
||||
#ifdef USE_DASH
|
||||
|
||||
uniform float dashSize;
|
||||
uniform float gapSize;
|
||||
|
||||
#endif
|
||||
|
||||
varying float vLineDistance;
|
||||
|
||||
#include <common>
|
||||
#include <color_pars_fragment>
|
||||
#include <fog_pars_fragment>
|
||||
#include <logdepthbuf_pars_fragment>
|
||||
#include <clipping_planes_pars_fragment>
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
|
||||
#include <clipping_planes_fragment>
|
||||
|
||||
#ifdef USE_DASH
|
||||
|
||||
if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps
|
||||
|
||||
if ( mod( vLineDistance, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
|
||||
|
||||
#endif
|
||||
|
||||
if ( abs( vUv.y ) > 1.0 ) {
|
||||
|
||||
float a = vUv.x;
|
||||
float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
|
||||
float len2 = a * a + b * b;
|
||||
|
||||
if ( len2 > 1.0 ) discard;
|
||||
|
||||
}
|
||||
|
||||
vec4 diffuseColor = vec4( diffuse, opacity );
|
||||
|
||||
#include <logdepthbuf_fragment>
|
||||
#include <color_fragment>
|
||||
|
||||
gl_FragColor = vec4( diffuseColor.rgb, diffuseColor.a );
|
||||
|
||||
#include <tonemapping_fragment>
|
||||
#include <fog_fragment>
|
||||
#include <premultiplied_alpha_fragment>
|
||||
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
class ConditionalLineMaterial extends LineMaterial {
|
||||
constructor(parameters) {
|
||||
super({
|
||||
type: 'ConditionalLineMaterial',
|
||||
|
||||
uniforms: UniformsUtils.clone(shader.uniforms),
|
||||
|
||||
vertexShader: shader.vertexShader,
|
||||
fragmentShader: shader.fragmentShader,
|
||||
|
||||
clipping: true // required for clipping support
|
||||
})
|
||||
|
||||
this.dashed = false
|
||||
|
||||
Object.defineProperties(this, {
|
||||
color: {
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
return this.uniforms.diffuse.value
|
||||
},
|
||||
|
||||
set: function (value) {
|
||||
this.uniforms.diffuse.value = value
|
||||
}
|
||||
},
|
||||
|
||||
linewidth: {
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
return this.uniforms.linewidth.value
|
||||
},
|
||||
|
||||
set: function (value) {
|
||||
this.uniforms.linewidth.value = value
|
||||
}
|
||||
},
|
||||
|
||||
dashScale: {
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
return this.uniforms.dashScale.value
|
||||
},
|
||||
|
||||
set: function (value) {
|
||||
this.uniforms.dashScale.value = value
|
||||
}
|
||||
},
|
||||
|
||||
dashSize: {
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
return this.uniforms.dashSize.value
|
||||
},
|
||||
|
||||
set: function (value) {
|
||||
this.uniforms.dashSize.value = value
|
||||
}
|
||||
},
|
||||
|
||||
gapSize: {
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
return this.uniforms.gapSize.value
|
||||
},
|
||||
|
||||
set: function (value) {
|
||||
this.uniforms.gapSize.value = value
|
||||
}
|
||||
},
|
||||
|
||||
opacity: {
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
return this.uniforms.opacity.value
|
||||
},
|
||||
|
||||
set: function (value) {
|
||||
this.uniforms.opacity.value = value
|
||||
}
|
||||
},
|
||||
|
||||
resolution: {
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
return this.uniforms.resolution.value
|
||||
},
|
||||
|
||||
set: function (value) {
|
||||
this.uniforms.resolution.value.copy(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.setValues(parameters)
|
||||
}
|
||||
}
|
||||
|
||||
ConditionalLineMaterial.prototype.isConditionalLineMaterial = true
|
||||
|
||||
export { ConditionalLineMaterial }
|
||||
@@ -0,0 +1,39 @@
|
||||
import * as THREE from 'three'
|
||||
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js'
|
||||
|
||||
export class ConditionalLineSegmentsGeometry extends LineSegmentsGeometry {
|
||||
fromConditionalEdgesGeometry(geometry) {
|
||||
super.fromEdgesGeometry(geometry)
|
||||
|
||||
const { direction, control0, control1 } = geometry.attributes
|
||||
|
||||
this.setAttribute(
|
||||
'direction',
|
||||
new THREE.InterleavedBufferAttribute(
|
||||
new THREE.InstancedInterleavedBuffer(direction.array, 6, 1),
|
||||
3,
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
this.setAttribute(
|
||||
'control0',
|
||||
new THREE.InterleavedBufferAttribute(
|
||||
new THREE.InstancedInterleavedBuffer(control0.array, 6, 1),
|
||||
3,
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
this.setAttribute(
|
||||
'control1',
|
||||
new THREE.InterleavedBufferAttribute(
|
||||
new THREE.InstancedInterleavedBuffer(control1.array, 6, 1),
|
||||
3,
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { BufferAttribute, BufferGeometry, Vector3 } from 'three'
|
||||
|
||||
const vec = new Vector3()
|
||||
export class OutsideEdgesGeometry extends BufferGeometry {
|
||||
constructor(geometry) {
|
||||
super()
|
||||
|
||||
const edgeInfo = {}
|
||||
const index = geometry.index
|
||||
const position = geometry.attributes.position
|
||||
for (let i = 0, l = index.count; i < l; i += 3) {
|
||||
const indices = [index.getX(i + 0), index.getX(i + 1), index.getX(i + 2)]
|
||||
|
||||
for (let j = 0; j < 3; j++) {
|
||||
const index0 = indices[j]
|
||||
const index1 = indices[(j + 1) % 3]
|
||||
|
||||
const hash = `${index0}_${index1}`
|
||||
const reverseHash = `${index1}_${index0}`
|
||||
if (reverseHash in edgeInfo) {
|
||||
delete edgeInfo[reverseHash]
|
||||
} else {
|
||||
edgeInfo[hash] = [index0, index1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const edgePositions = []
|
||||
for (const key in edgeInfo) {
|
||||
const [i0, i1] = edgeInfo[key]
|
||||
|
||||
vec.fromBufferAttribute(position, i0)
|
||||
edgePositions.push(vec.x, vec.y, vec.z)
|
||||
|
||||
vec.fromBufferAttribute(position, i1)
|
||||
edgePositions.push(vec.x, vec.y, vec.z)
|
||||
}
|
||||
|
||||
this.setAttribute(
|
||||
'position',
|
||||
new BufferAttribute(new Float32Array(edgePositions), 3, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,12 @@ import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
|
||||
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
|
||||
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
||||
|
||||
export type MaterialMode = 'original' | 'normal' | 'wireframe' | 'depth'
|
||||
export type MaterialMode =
|
||||
| 'original'
|
||||
| 'normal'
|
||||
| 'wireframe'
|
||||
| 'depth'
|
||||
| 'lineart'
|
||||
export type UpDirection = 'original' | '-x' | '+x' | '-y' | '+y' | '-z' | '+z'
|
||||
export type CameraType = 'perspective' | 'orthographic'
|
||||
|
||||
|
||||
@@ -858,6 +858,7 @@
|
||||
"scene": "Scene",
|
||||
"model": "Model",
|
||||
"camera": "Camera",
|
||||
"light": "Light"
|
||||
"light": "Light",
|
||||
"switchingMaterialMode": "Switching Material Mode..."
|
||||
}
|
||||
}
|
||||
@@ -335,6 +335,7 @@
|
||||
"scene": "Scène",
|
||||
"showGrid": "Afficher la grille",
|
||||
"switchCamera": "Changer de caméra",
|
||||
"switchingMaterialMode": "Changement de mode de matériau...",
|
||||
"upDirection": "Direction Haut",
|
||||
"uploadBackgroundImage": "Télécharger l'image de fond"
|
||||
},
|
||||
|
||||
@@ -335,6 +335,7 @@
|
||||
"scene": "シーン",
|
||||
"showGrid": "グリッドを表示",
|
||||
"switchCamera": "カメラを切り替える",
|
||||
"switchingMaterialMode": "マテリアルモードの切り替え中...",
|
||||
"upDirection": "上方向",
|
||||
"uploadBackgroundImage": "背景画像をアップロード"
|
||||
},
|
||||
|
||||
@@ -335,6 +335,7 @@
|
||||
"scene": "장면",
|
||||
"showGrid": "그리드 표시",
|
||||
"switchCamera": "카메라 전환",
|
||||
"switchingMaterialMode": "재료 모드 전환 중...",
|
||||
"upDirection": "위 방향",
|
||||
"uploadBackgroundImage": "배경 이미지 업로드"
|
||||
},
|
||||
|
||||
@@ -335,6 +335,7 @@
|
||||
"scene": "Сцена",
|
||||
"showGrid": "Показать сетку",
|
||||
"switchCamera": "Переключить камеру",
|
||||
"switchingMaterialMode": "Переключение режима материала...",
|
||||
"upDirection": "Направление Вверх",
|
||||
"uploadBackgroundImage": "Загрузить фоновое изображение"
|
||||
},
|
||||
|
||||
@@ -335,6 +335,7 @@
|
||||
"scene": "场景",
|
||||
"showGrid": "显示网格",
|
||||
"switchCamera": "切换摄像头",
|
||||
"switchingMaterialMode": "切换材质模式中...",
|
||||
"upDirection": "向上方向",
|
||||
"uploadBackgroundImage": "上传背景图片"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user