scene observer

This commit is contained in:
Kseninia Mikhaylova 2025-03-20 10:56:03 +03:00
parent 5c48f41fa6
commit fea664c209
5 changed files with 195 additions and 147 deletions

View File

@ -27,21 +27,18 @@ const cameraStat = reactive({
</div> </div>
</template> </template>
<Loader /> <Loader />
<TresCanvas clear-color="#e2e8f0"> <Scene :canvasProps="{ clearColor: '#e2e8f0' }">
<TresPerspectiveCamera v-bind="cameraStat" ref="camera" /> <TresPerspectiveCamera v-bind="cameraStat" ref="camera" />
<OrbitControls v-bind="controlsState" make-default /> <OrbitControls v-bind="controlsState" make-default />
<Suspense> <Suspense>
<ModelSmoothCamera /> <ModelSmoothCamera />
</Suspense> </Suspense>
<Suspense>
<ModelEnv />
</Suspense>
<TresGroup> <TresGroup>
<Suspense> <Suspense>
<ModelParametric /> <ModelParametric />
</Suspense> </Suspense>
</TresGroup> </TresGroup>
</TresCanvas> </Scene>
</ClientOnly> </ClientOnly>
</div> </div>
</template> </template>

View File

@ -17,22 +17,6 @@ const explosion_state = use_explosion_state()
const toggleExpState = () => { const toggleExpState = () => {
explosion_state.value = !explosion_state.value explosion_state.value = !explosion_state.value
} }
const back_light = ref()
const secondary_light = ref()
const loadAll = async () => {
const { scene: back } = await useGLTF('/models_light/back_exp.glb')
const { scene: secondary } = await useGLTF('/models_light/secondary_exp.glb')
const k = 0.03
back_light.value = back.children[0]
back_light.value.intensity = back_light.value.intensity * k
back_light.value.shadow.bias = -0.02
secondary_light.value = secondary.children[0]
secondary_light.value.intensity = secondary_light.value.intensity * k
secondary_light.value.shadow.bias = -0.01
}
const changeDistance = (v = 1) => { const changeDistance = (v = 1) => {
if (camera.value && controls.value) { if (camera.value && controls.value) {
@ -41,9 +25,6 @@ const changeDistance = (v = 1) => {
camera.value.position.normalize().multiplyScalar(r) camera.value.position.normalize().multiplyScalar(r)
} }
} }
onMounted(() => {
loadAll()
})
</script> </script>
<template> <template>
<div class="h-96 relative"> <div class="h-96 relative">
@ -53,14 +34,11 @@ onMounted(() => {
Загрузка 3D модели Загрузка 3D модели
</div> </div>
</template> </template>
<TresCanvas height="600"> <Scene :canvasProps="{ height: '600' }">
<TresPerspectiveCamera :position="[-7, 2, 4]" ref="camera" /> <TresPerspectiveCamera :position="[-7, 2, 4]" ref="camera" />
<OrbitControls v-bind="controlsState" ref="controls" make-default /> <OrbitControls v-bind="controlsState" ref="controls" make-default />
<ModelEnv /> <ModelDiagram />
<Suspense> </Scene>
<ModelDiagram />
</Suspense>
</TresCanvas>
</ClientOnly> </ClientOnly>
<div class="canvas-icons"> <div class="canvas-icons">
<a href="#" @click.prevent="toggleExpState"> <a href="#" @click.prevent="toggleExpState">

View File

@ -22,131 +22,149 @@ const { seek, seekAll } = useSeek()
const globalFenceType = useGlobalFenceType() const globalFenceType = useGlobalFenceType()
const topper_models = { const top = ref(null)
baseToppers: {} as { [key: toppersIds]: Object3D },
aristoToppers: {} as { [key: toppersIds]: Object3D },
}
for (const key of Object.keys(allToppers)) {
let toppers = allToppers[key]
for await (const element of toppers) {
const { scene } = await useGLTF(getModel(element.id, key))
topper_models[key][element.id] = scene
}
}
const { scene: model_pillar_center } = await useGLTF('/models_one/pillar/center.glb')
const { scene: model_pillar_bottom } = await useGLTF('/models_one/pillar/bottom.glb')
const { scene: model_pillar_inner } = await useGLTF('/models_one/pillar/inner.glb')
const { scene: model_pillar_brace } = await useGLTF('/models_one/pillar/brace.glb')
const { scene: model_fastening_top } = await useGLTF('/models_one/fastening/top.glb');
const { scene: model_fastening_side } = await useGLTF('/models_one/fastening/side.glb');
const { scene: model_fixing } = await useGLTF('/models_one/fixing.glb');
const { scene: top_model } = await useGLTF('/models_one/top_100.glb', { draco: true })
// const { scene: lamelle_model } = await useGLTF('/models_one/lamel_100.glb', { draco: true });
const { scene: lamelle_model_standart } = await useGLTF('/models_one/lamel_100.glb', { draco: true });
const { scene: lamelle_model_aristo } = await useGLTF('/models_one/lamel_100_aristo.glb', { draco: true });
const top = ref(top_model)
const pillar_top = ref() const pillar_top = ref()
const pillar_center = ref(model_pillar_center) const pillar_center = ref(null)
const pillar_bottom = ref(model_pillar_bottom) const pillar_bottom = ref(null)
const pillar_inner = ref(model_pillar_inner) const pillar_inner = ref(null)
const pillar_brace = ref(model_pillar_brace) const pillar_brace = ref(null)
const fastening_top = ref(model_fastening_top) const fastening_top = ref(null)
const fastening_side = ref(model_fastening_side) const fastening_side = ref(null)
const fixing = ref(model_fixing) const fixing = ref(null)
const lamelle = ref(lamelle_model_standart) const lamelle = ref(null)
const setPillarTopper = () => { const total = ref(0)
let key = 'baseToppers' const size = ref(0)
if (globalFenceType.value?.type == 'aristo') { const count = ref(0)
key = 'aristoToppers'
}
pillar_top.value = topper_models[key][pillar_topper.value]
}
setPillarTopper()
watch(pillar_topper, setPillarTopper)
const total = ref((section_count.value + ~~(!!extra_section.value)))
const size = ref(Math.ceil(total.value / 4))
const count = ref((total.value >= 4) ? size.value : total.value)
watch(() => [section_count.value, extra_section.value], () => {
total.value = (section_count.value + ~~(!!extra_section.value))
size.value = Math.ceil(total.value / 4);
count.value = (total.value >= 4) ? size.value : total.value;
const lines_count = (total.value >= 4) ? 4 : 1
const base = seek(scene.value, 'name', 'base')
if (base?.children && base.children.length !== lines_count) {
base.children = [...base?.children.slice(0, lines_count)]
}
const lines = seekAll(scene.value, 'name', 'line')
lines.forEach(line => {
let n = size.value
if (lines_count == 1) {
n = total.value
}
if (line.name.endsWith('_4')) {
n = total.value - size.value * 3
if (n < 0) {
n = 0
}
}
const inner = seek(line, 'name', line.name + '_inner');
if (inner?.children && n < inner?.children.length) {
inner.children = [...inner?.children.slice(0, n)]
}
});
})
const setTarget = (smooth = false) => {
let f = fence_section.value * lamelles_count.value * lamelle_height.value * 0.75;
const max = 2.25
if (f < max) f = max
const target = new Vector3(0, lamelles_count.value * lamelle_height.value * 0.5, 0);
if (smooth) {
goto_target.value = target
goto_cam.value = new Vector3(f, f, f)
} else {
(controls.value as OrbitControlsProps).target = target;
(controls.value as any).update()
goto_cam.value = new Vector3(f, f, f)
}
}
watch([lamelles_count, fence_section, lamelle_height], () => {
setTarget()
open_calc.value = []
})
watch(open_calc, () => {
if (Object.keys(open_calc.value).length == 0) {
setTarget(true)
}
})
const min_for_square = 12; const min_for_square = 12;
setTarget() const loadAll = async () => {
const topper_models = {
baseToppers: {} as { [key: toppersIds]: Object3D },
aristoToppers: {} as { [key: toppersIds]: Object3D },
}
for (const key of Object.keys(allToppers)) {
let toppers = allToppers[key]
for await (const element of toppers) {
const { scene } = await useGLTF(getModel(element.id, key))
topper_models[key][element.id] = scene
}
}
const setLamelleType = () => { const { scene: model_pillar_center } = await useGLTF('/models_one/pillar/center.glb')
if (globalFenceType.value?.type == 'standart') { const { scene: model_pillar_bottom } = await useGLTF('/models_one/pillar/bottom.glb')
lamelle.value = lamelle_model_standart const { scene: model_pillar_inner } = await useGLTF('/models_one/pillar/inner.glb')
lamelle_height.value = 0.115 const { scene: model_pillar_brace } = await useGLTF('/models_one/pillar/brace.glb')
pillar_topper.value = 0
setPillarTopper() const { scene: model_fastening_top } = await useGLTF('/models_one/fastening/top.glb');
const { scene: model_fastening_side } = await useGLTF('/models_one/fastening/side.glb');
const { scene: model_fixing } = await useGLTF('/models_one/fixing.glb');
const { scene: top_model } = await useGLTF('/models_one/top_100.glb', { draco: true })
// const { scene: lamelle_model } = await useGLTF('/models_one/lamel_100.glb', { draco: true });
const { scene: lamelle_model_standart } = await useGLTF('/models_one/lamel_100.glb', { draco: true });
const { scene: lamelle_model_aristo } = await useGLTF('/models_one/lamel_100_aristo.glb', { draco: true });
top.value = top_model
pillar_center.value = model_pillar_center
pillar_bottom.value = model_pillar_bottom
pillar_inner.value = model_pillar_inner
pillar_brace.value = model_pillar_brace
fastening_top.value = model_fastening_top
fastening_side.value = model_fastening_side
fixing.value = model_fixing
lamelle.value = lamelle_model_standart
const setPillarTopper = () => {
let key = 'baseToppers'
if (globalFenceType.value?.type == 'aristo') {
key = 'aristoToppers'
}
pillar_top.value = topper_models[key][pillar_topper.value]
} }
if (globalFenceType.value?.type == 'aristo') { setPillarTopper()
lamelle.value = lamelle_model_aristo watch(pillar_topper, setPillarTopper)
lamelle_height.value = 0.196
pillar_topper.value = 0 total.value = (section_count.value + ~~(!!extra_section.value))
setPillarTopper() size.value = Math.ceil(total.value / 4)
count.value = (total.value >= 4) ? size.value : total.value
watch(() => [section_count.value, extra_section.value], () => {
total.value = (section_count.value + ~~(!!extra_section.value))
size.value = Math.ceil(total.value / 4);
count.value = (total.value >= 4) ? size.value : total.value;
const lines_count = (total.value >= 4) ? 4 : 1
const base = seek(scene.value, 'name', 'base')
if (base?.children && base.children.length !== lines_count) {
base.children = [...base?.children.slice(0, lines_count)]
}
const lines = seekAll(scene.value, 'name', 'line')
lines.forEach(line => {
let n = size.value
if (lines_count == 1) {
n = total.value
}
if (line.name.endsWith('_4')) {
n = total.value - size.value * 3
if (n < 0) {
n = 0
}
}
const inner = seek(line, 'name', line.name + '_inner');
if (inner?.children && n < inner?.children.length) {
inner.children = [...inner?.children.slice(0, n)]
}
});
})
const setTarget = (smooth = false) => {
let f = fence_section.value * lamelles_count.value * lamelle_height.value * 0.75;
const max = 2.25
if (f < max) f = max
const target = new Vector3(0, lamelles_count.value * lamelle_height.value * 0.5, 0);
if (smooth) {
goto_target.value = target
goto_cam.value = new Vector3(f, f, f)
} else {
(controls.value as OrbitControlsProps).target = target;
(controls.value as any).update()
goto_cam.value = new Vector3(f, f, f)
}
} }
watch([lamelles_count, fence_section, lamelle_height], () => {
setTarget()
open_calc.value = []
})
watch(open_calc, () => {
if (Object.keys(open_calc.value).length == 0) {
setTarget(true)
}
})
setTarget()
const setLamelleType = () => {
if (globalFenceType.value?.type == 'standart') {
lamelle.value = lamelle_model_standart
lamelle_height.value = 0.115
pillar_topper.value = 0
setPillarTopper()
}
if (globalFenceType.value?.type == 'aristo') {
lamelle.value = lamelle_model_aristo
lamelle_height.value = 0.196
pillar_topper.value = 0
setPillarTopper()
}
}
setLamelleType()
watch(() => globalFenceType.value?.id, setLamelleType)
} }
setLamelleType() onMounted(async () => { await loadAll() })
watch(() => globalFenceType.value?.id, setLamelleType)
</script> </script>
<template> <template>
<TresGroup name="base"> <TresGroup name="base">

55
components/scene.vue Normal file
View File

@ -0,0 +1,55 @@
<script setup>
const props = defineProps({
canvasProps: {
type: Object,
default: () => ({}),
},
cameraProps: {
type: Object,
default: () => ({}),
}
});
const container = ref(null);
const isIntersecting = ref(true);
let observer;
// Вычисляем renderMode на основе видимости
const renderMode = computed(() => (isIntersecting.value ? 'always' : 'never'));
const startObserver = () => {
observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
isIntersecting.value = entry.isIntersecting;
});
},
{ threshold: 0.1 } // Настройте порог видимости по вашему усмотрению
);
if (container.value) {
observer.observe(container.value);
}
}
onMounted(async () => {
await nextTick(); // Ждём завершения рендеринга
startObserver(); // Запускаем IntersectionObserver
});
onBeforeUnmount(() => {
if (observer) {
observer.disconnect();
}
});
</script>
<template>
<div ref="container" class="h-full">
<Suspense>
<TresCanvas v-bind="canvasProps" :render-mode="renderMode" :key="renderMode">
<ModelEnv />
<Suspense>
<slot />
</Suspense>
</TresCanvas>
</Suspense>
</div>
</template>

View File

@ -29,7 +29,7 @@ export default defineNuxtConfig({
runtimeConfig: { runtimeConfig: {
public: { public: {
// apiBase: process.env.mode == 'DEVELOPMENT' ? "http://localhost:8000" : "https://mns.kustarshina.ru/kp", // apiBase: process.env.mode == 'DEVELOPMENT' ? "http://localhost:8000" : "https://mns.kustarshina.ru/kp",
apiBase: process.env.mode == 'DEVELOPMENT' ? "http://mns.dev.kustarshina.ru/kp" : "https://mns.kustarshina.ru/kp", apiBase: process.env.mode == 'DEVELOPMENT' ? "http://mns.dev.kustarshina.ru" : "https://mns.kustarshina.ru/kp",
// apiBase: 'http://localhost:8000', // apiBase: 'http://localhost:8000',
imgBase: 'https://mns.kustarshina.ru', imgBase: 'https://mns.kustarshina.ru',
baseUrl: '', baseUrl: '',