Compare commits

..

40 Commits

Author SHA1 Message Date
ksenia_mikhailova 1cf4c40878 Merge pull request 'dev' (#90) from dev into main
Deploy / build_and_push_images (push) Has been cancelled Details
Deploy / deploy_to_server_dev (push) Has been cancelled Details
Reviewed-on: #90
2025-03-20 14:15:17 +03:00
ksenia_mikhailova 50f61e9341 Merge pull request 'debouncing' (#89) from bx-2376-new_type into dev
Deploy / build_and_push_images (push) Has been cancelled Details
Deploy / deploy_to_server_dev (push) Has been cancelled Details
Reviewed-on: #89
2025-03-20 12:40:37 +03:00
Kseninia Mikhaylova 9cad1c4103 debouncing 2025-03-20 12:38:05 +03:00
ksenia_mikhailova 01cb9440b2 Merge pull request 'debouncing' (#88) from bx-2376-new_type into dev
Deploy / build_and_push_images (push) Waiting to run Details
Deploy / deploy_to_server_dev (push) Blocked by required conditions Details
Reviewed-on: #88
2025-03-20 12:16:16 +03:00
Kseninia Mikhaylova 88b3651415 debouncing 2025-03-20 12:15:28 +03:00
ksenia_mikhailova 27c35b5a32 Merge pull request 'env suspense' (#87) from bx-2376-new_type into dev
Deploy / build_and_push_images (push) Waiting to run Details
Deploy / deploy_to_server_dev (push) Blocked by required conditions Details
Reviewed-on: #87
2025-03-20 11:06:29 +03:00
Kseninia Mikhaylova 655f2f6ec4 env suspense 2025-03-20 11:06:04 +03:00
ksenia_mikhailova 21ae73f0f6 Merge pull request 'bx-2376-new_type' (#86) from bx-2376-new_type into dev
Deploy / build_and_push_images (push) Waiting to run Details
Deploy / deploy_to_server_dev (push) Blocked by required conditions Details
Reviewed-on: #86
2025-03-20 10:57:19 +03:00
Kseninia Mikhaylova fea664c209 scene observer 2025-03-20 10:56:03 +03:00
Kseninia Mikhaylova 5c48f41fa6 add icon 2025-03-13 13:53:58 +03:00
Kseninia Mikhaylova 04fa4e6ba8 backward slash 2025-03-12 16:12:34 +03:00
Kseninia Mikhaylova e3814b150c radiobtn style 2025-03-12 09:45:57 +03:00
Kseninia Mikhaylova 92535f6f17 radiobtn style 2025-03-12 09:41:22 +03:00
Kseninia Mikhaylova ee2a26907c new exp 2025-02-06 12:57:14 +03:00
Kseninia Mikhaylova c471e49418 bottom model 2025-02-06 12:29:07 +03:00
Kseninia Mikhaylova 052f4d6e41 aristo 2025-02-04 10:38:47 +03:00
Kseninia Mikhaylova d10e3392e7 models 2025-02-04 09:44:50 +03:00
Kseninia Mikhaylova 5f571fb244 model repainting 2025-02-03 10:15:49 +03:00
Kseninia Mikhaylova 1bd900ec4c tres find 2025-01-30 17:00:19 +03:00
Kseninia Mikhaylova c9d1e2d02b labels 2025-01-30 08:38:40 +03:00
Kseninia Mikhaylova 799eab7dee reactive calc 2025-01-29 15:25:42 +03:00
ksenia_mikhailova e063154a1b Merge pull request 'remove azimuth' (#85) from dev into main
Deploy / build_and_push_images (push) Successful in 1m35s Details
Deploy / deploy_to_server_dev (push) Successful in 33s Details
Reviewed-on: #85
2024-10-03 15:51:42 +03:00
Kseninia Mikhaylova c1a83a2efc remove azimuth
Deploy / build_and_push_images (push) Successful in 1m27s Details
Deploy / deploy_to_server_dev (push) Successful in 37s Details
2024-10-03 15:50:50 +03:00
ksenia_mikhailova 1c74647a21 Merge pull request 'dev' (#84) from dev into main
Deploy / build_and_push_images (push) Successful in 1m31s Details
Deploy / deploy_to_server_dev (push) Successful in 39s Details
Reviewed-on: #84
2024-10-03 15:30:22 +03:00
ksenia_mikhailova 9bac0c3ff7 Merge pull request 'privacy check' (#63) from dev into main
Deploy / build_and_push_images (push) Successful in 2m21s Details
Deploy / deploy_to_server_dev (push) Successful in 1m9s Details
Reviewed-on: #63
2024-08-28 08:44:09 +03:00
ksenia_mikhailova ac24ec9b5d Merge pull request 'composer fix' (#62) from dev into main
Deploy / build_and_push_images (push) Successful in 1m29s Details
Deploy / deploy_to_server_dev (push) Successful in 39s Details
Reviewed-on: #62
2024-07-26 10:45:04 +03:00
ksenia_mikhailova 85fcc911f4 Merge pull request 'dev' (#60) from dev into main
Deploy / build_and_push_images (push) Successful in 1m27s Details
Deploy / deploy_to_server_dev (push) Failing after 35s Details
Reviewed-on: #60
2024-07-26 10:41:36 +03:00
ksenia_mikhailova 77a4ac2baf Merge pull request 'dev' (#43) from dev into main
Deploy / build_and_push_images (push) Successful in 1m37s Details
Deploy / deploy_to_server_dev (push) Successful in 37s Details
Reviewed-on: #43
2024-07-24 14:22:26 +03:00
ksenia_mikhailova a15aaa9a1a Merge pull request 'old logo' (#32) from dev into main
Deploy / build_and_push_images (push) Successful in 1m42s Details
Deploy / deploy_to_server_dev (push) Successful in 34s Details
Reviewed-on: #32
2024-07-16 13:43:01 +03:00
ksenia_mikhailova 3b4a79061a Merge pull request 'dev' (#29) from dev into main
Deploy / build_and_push_images (push) Successful in 1m39s Details
Deploy / deploy_to_server_dev (push) Successful in 1m3s Details
Reviewed-on: #29
2024-07-15 11:50:28 +03:00
ksenia_mikhailova bff2219193 Merge pull request 'dev' (#27) from dev into main
Deploy / build_and_push_images (push) Successful in 1m47s Details
Deploy / deploy_to_server_dev (push) Successful in 1m13s Details
Reviewed-on: #27
2024-07-15 10:41:50 +03:00
ksenia_mikhailova dc1e491d1f Merge pull request 'dev' (#26) from dev into main
Deploy / build_and_push_images (push) Successful in 1m50s Details
Deploy / deploy_to_server_dev (push) Successful in 35s Details
Reviewed-on: #26
2024-07-15 09:00:47 +03:00
ksenia_mikhailova 79ee614db8 Merge pull request 'env' (#19) from dev into main
Deploy / build_and_push_images (push) Successful in 1m23s Details
Deploy / deploy_to_server_dev (push) Successful in 32s Details
Reviewed-on: #19
2024-07-08 16:23:02 +03:00
ksenia_mikhailova 40ab9d82db Merge pull request 'env' (#18) from dev into main
Deploy / build_and_push_images (push) Successful in 1m25s Details
Deploy / deploy_to_server_dev (push) Successful in 32s Details
Reviewed-on: #18
2024-07-08 16:20:13 +03:00
ksenia_mikhailova 786642bcc0 Merge pull request 'env' (#17) from dev into main
Deploy / build_and_push_images (push) Failing after 1m14s Details
Deploy / deploy_to_server_dev (push) Has been skipped Details
Reviewed-on: #17
2024-07-08 16:17:41 +03:00
ksenia_mikhailova 98a4bcbf06 Merge pull request 'test new docker build' (#16) from dev into main
Deploy / build_and_push_images (push) Successful in 6m25s Details
Deploy / deploy_to_server_dev (push) Successful in 32s Details
Reviewed-on: #16
2024-07-08 16:02:26 +03:00
ksenia_mikhailova a0a38a9d25 Merge pull request 'robots and prod build' (#15) from dev into main
Deploy / build_and_push_images (push) Successful in 1m25s Details
Deploy / deploy_to_server_dev (push) Successful in 31s Details
Reviewed-on: #15
2024-07-08 09:24:53 +03:00
ksenia_mikhailova 5b9d288a57 Merge pull request 'Production' (#13) from dev into main
Reviewed-on: #13
2024-07-08 09:23:31 +03:00
ksenia_mikhailova e6ad912e85 Merge pull request 'bx-843-testing' (#3) from bx-843-testing into main
Reviewed-on: #3
2024-07-04 13:48:13 +03:00
ksenia_mikhailova b90035130c Merge pull request 'dev' (#2) from dev into main
Reviewed-on: #2
2024-07-04 10:47:24 +03:00
31 changed files with 636 additions and 258 deletions

View File

@ -6,6 +6,5 @@ import '@/assets/main.scss'
<Header /> <Header />
<NuxtPage /> <NuxtPage />
<Footer /> <Footer />
<Modal />
</div> </div>
</template> </template>

View File

@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { DoubleSide, PointLight } from 'three';
import { degToRad } from 'three/src/math/MathUtils.js';
import { TresCanvas } from '@tresjs/core' import { TresCanvas } from '@tresjs/core'
import { Stats, OrbitControls } from '@tresjs/cientos' import { Stats, OrbitControls } from '@tresjs/cientos'
import { degToRad } from 'three/src/math/MathUtils.js';
import gaussMaterial from '~/utils/gauss_material'; const container = ref<HTMLElement | null>(null);
const { isIntersecting, startObserver, stopObserver } = useSceneVisibility();
const controlsState = reactive({ const controlsState = reactive({
position: { x: 0, y: 0, z: 0 }, position: { x: 0, y: 0, z: 0 },
@ -12,17 +12,29 @@ const controlsState = reactive({
enableZoom: false, enableZoom: false,
minPolarAngle: degToRad(60), minPolarAngle: degToRad(60),
maxPolarAngle: degToRad(100), maxPolarAngle: degToRad(100),
// minAzimuthAngle: degToRad(0), //minAzimuthAngle: degToRad(0),
// maxAzimuthAngle: degToRad(180), //maxAzimuthAngle: degToRad(180),
}) })
const cameraStat = reactive({ const cameraStat = reactive({
position: [0, 0, 5], position: [0, 0, 5],
aspect: 1920 / 600, aspect: 1920 / 600,
// fov: 40, // fov: 40,
}) })
const renderMode = computed(() => (isIntersecting.value ? 'always' : 'manual'));
onMounted(async () => {
if (container.value) {
await nextTick()
startObserver(container.value);
}
});
onBeforeUnmount(() => {
stopObserver();
});
</script> </script>
<template> <template>
<div class="calc"> <div class="calc" ref="container">
<ClientOnly fallback-tag="div"> <ClientOnly fallback-tag="div">
<template #fallback> <template #fallback>
<div class="fallback"> <div class="fallback">
@ -30,14 +42,14 @@ const cameraStat = reactive({
</div> </div>
</template> </template>
<Loader /> <Loader />
<TresCanvas clear-color="#e2e8f0"> <TresCanvas clear-color="#e2e8f0" :render-mode="renderMode" :key="renderMode">
<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 /> <ModelEnv />
</Suspense> </Suspense>
<Suspense> <Suspense>
<ModelEnv /> <ModelSmoothCamera />
</Suspense> </Suspense>
<TresGroup> <TresGroup>
<Suspense> <Suspense>

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { Vector3 } from 'three'; import { Vector3 } from 'three';
import { getColorNameFromRal } from '@/components/ral'
import type { ralTypes } from '@/components/ral' import type { ralTypes } from '@/components/ral'
import { predefLamelleColors, predefPillarColors } from '~/composables/useCalc'; import { predefLamelleColors, predefPillarColors } from '~/composables/useCalc';
@ -18,6 +19,7 @@ const section_count = use_section_count()
const extra_section = use_extra_section() const extra_section = use_extra_section()
const total_length = use_total_length() const total_length = use_total_length()
const min_length = use_min_length() const min_length = use_min_length()
const globalFenceType = useGlobalFenceType()
if (!pillar_color.value) { if (!pillar_color.value) {
const r = Math.floor(Math.random() * predefPillarColors.length) const r = Math.floor(Math.random() * predefPillarColors.length)
@ -204,7 +206,8 @@ const calc_table = computed(() => {
:goto_cam="new Vector3(1, 2, -1)" /> :goto_cam="new Vector3(1, 2, -1)" />
<label for="pillar_topper">Колпак столба</label> <label for="pillar_topper">Колпак столба</label>
<DropdownPicker type="topper" :cb="setPillarTopper" name="pillar_topper" <DropdownPicker type="topper" :cb="setPillarTopper" name="pillar_topper"
:pattern="getTopper(pillar_topper)" :disabled="remove_pillar" :pattern="getTopper(pillar_topper, globalFenceType?.type == 'aristo' ? 'aristoToppers' : 'baseToppers')"
:disabled="remove_pillar"
:goto_target="new Vector3(fence_section * -0.5, lamelles_count * lamelle_height, 0)" :goto_target="new Vector3(fence_section * -0.5, lamelles_count * lamelle_height, 0)"
:goto_cam="new Vector3(-1, 2, 1)" /> :goto_cam="new Vector3(-1, 2, 1)" />
</div> </div>

View File

@ -1,24 +1,39 @@
<script setup lang="ts"> <script setup lang="ts">
import { getFilename as getPattern, patterns } from '../pattern'; import { getFilename as getPattern, patterns } from '../pattern';
import { getFilename as getTopper, toppers } from '../topper'; import { getFilename as getTopper, baseToppers, aristoToppers } from '../topper';
const globalFenceType = useGlobalFenceType()
const props = defineProps(['cb', 'patterns']); const props = defineProps(['cb', 'patterns']);
type pType = { type pType = {
list: typeof patterns | typeof toppers, list: typeof patterns | typeof baseToppers,
func: any func: any
type: string | null
} }
const p = { const p = reactive({
list: [], list: [],
type: null,
func: () => { } func: () => { }
} as pType } as pType)
if (props.patterns == 'pattern') { if (props.patterns == 'pattern') {
p.list = patterns; p.list = patterns;
p.func = getPattern p.func = getPattern
} else if (props.patterns == 'topper') { } else if (props.patterns == 'topper') {
p.list = toppers p.list = baseToppers
p.func = getTopper p.func = getTopper
} }
watch(globalFenceType, () => {
if (props.patterns == 'topper') {
if (globalFenceType.value?.type == 'standart') {
p.list = baseToppers
p.type = 'toppers'
}
if (globalFenceType.value?.type == 'aristo') {
p.list = aristoToppers
p.type = 'aristoToppers'
}
}
})
</script> </script>
<template> <template>
<template v-for="item in p.list"> <template v-for="item in p.list">

View File

@ -3,6 +3,9 @@ import { TresCanvas } from '@tresjs/core'
import { OrbitControls } from '@tresjs/cientos' import { OrbitControls } from '@tresjs/cientos'
import { Vector3 } from 'three'; import { Vector3 } from 'three';
const container = ref<HTMLElement | null>(null);
const { isIntersecting, startObserver, stopObserver } = useSceneVisibility();
const camera = ref() const camera = ref()
const controls = ref() const controls = ref()
const controlsState = reactive({ const controlsState = reactive({
@ -17,22 +20,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,42 +28,58 @@ const changeDistance = (v = 1) => {
camera.value.position.normalize().multiplyScalar(r) camera.value.position.normalize().multiplyScalar(r)
} }
} }
onMounted(() => {
loadAll() const renderMode = computed(() => (isIntersecting.value ? 'always' : 'manual'));
})
onMounted(async () => {
if (container.value) {
await nextTick()
startObserver(container.value);
}
});
onBeforeUnmount(() => {
stopObserver();
});
</script> </script>
<template> <template>
<div class="h-96 relative"> <Suspense>
<ClientOnly fallback-tag="div"> <div ref="container" class="h-96 relative">
<template #fallback> <ClientOnly fallback-tag="div">
<div class="fallback"> <template #fallback>
Загрузка 3D модели <div class="fallback">
</div> Загрузка 3D модели
</template> </div>
<TresCanvas height="600"> </template>
<TresPerspectiveCamera :position="[-7, 2, 4]" ref="camera" /> {{ renderMode }}
<OrbitControls v-bind="controlsState" ref="controls" make-default /> <TresCanvas height="600">
<ModelEnv /> <ModelUpdateRenderMode :render-mode="renderMode" />
<Suspense> <TresPerspectiveCamera :position="[-7, 2, 4]" ref="camera" />
<ModelDiagram /> <OrbitControls v-bind="controlsState" ref="controls" make-default />
</Suspense> <Suspense>
</TresCanvas> <ModelEnv />
</ClientOnly> </Suspense>
<div class="canvas-icons"> <Suspense>
<a href="#" @click.prevent="toggleExpState"> <ModelDiagram />
<Icon name="mdi:arrow-collapse-horizontal" v-if="explosion_state" /> </Suspense>
<Icon name="mdi:arrow-expand-horizontal" v-else /> </TresCanvas>
</a> </ClientOnly>
<a href="#" @click.prevent="changeDistance(-0.5)" <div class="canvas-icons">
:class="[{ 'disabled': camera ? (camera.position.distanceTo(new Vector3(0, 0, 0)) <= controlsState.minDistance) : null }]"> <a href="#" @click.prevent="toggleExpState">
<Icon name="mdi:plus-circle-outline" /> <Icon name="mdi:arrow-collapse-horizontal" v-if="explosion_state" />
</a> <Icon name="mdi:arrow-expand-horizontal" v-else />
<a href="#" @click.prevent="changeDistance(0.5)" </a>
:class="[{ 'disabled': camera ? (camera.position.distanceTo(new Vector3(0, 0, 0)) >= (controlsState.maxDistance)) : null }]"> <a href="#" @click.prevent="changeDistance(-0.5)"
<Icon name="mdi:minus-circle-outline" /> :class="[{ 'disabled': camera ? (camera.position.distanceTo(new Vector3(0, 0, 0)) <= controlsState.minDistance) : null }]">
</a> <Icon name="mdi:plus-circle-outline" />
</a>
<a href="#" @click.prevent="changeDistance(0.5)"
:class="[{ 'disabled': camera ? (camera.position.distanceTo(new Vector3(0, 0, 0)) >= (controlsState.maxDistance)) : null }]">
<Icon name="mdi:minus-circle-outline" />
</a>
</div>
</div> </div>
</div> </Suspense>
</template> </template>
<style scoped> <style scoped>

View File

@ -7,7 +7,7 @@ import { getName as getTopperName } from './topper';
const config = useRuntimeConfig() const config = useRuntimeConfig()
const apiBase = config.public.apiBase const apiBase = config.public.apiBase
const { data: calculatorData } = await apiFetch<ApiCalcType>(`calculator/5/`) const props = defineProps(['calcData'])
const isModalOpen = useState('modal_open', () => false) const isModalOpen = useState('modal_open', () => false)
const lamelle_height = useState<number>('lamelle_height') const lamelle_height = useState<number>('lamelle_height')
@ -122,12 +122,14 @@ const total_colors = computed(() => {
`Столбы ${pillar_color.value} ${getColorNameFromRal(pillar_color.value)}`, `Столбы ${pillar_color.value} ${getColorNameFromRal(pillar_color.value)}`,
] ]
}) })
const total_txt = computed(() => { const total_txt = computed(() => {
if (!calculatorData.value) return const calculatorData = props.calcData as ApiCalcType
const pillar = parseFloat(calculatorData.value.pillar) if (!calculatorData) return
const pillar_base = parseFloat(calculatorData.value.pillar_base) const pillar = parseFloat(calculatorData.pillar)
const lamelles_block = parseFloat(calculatorData.value.lamelles_block) const pillar_base = parseFloat(calculatorData.pillar_base)
const { discount } = calculatorData.value const lamelles_block = parseFloat(calculatorData.lamelles_block)
const { discount } = calculatorData
const sections = section_count.value as number const sections = section_count.value as number
const extra_m = extra_section.value as number * 0.001 const extra_m = extra_section.value as number * 0.001

View File

@ -4,6 +4,7 @@ import { useGLTF } from '@tresjs/cientos'
import { getColorHexFromRal } from '../ral'; import { getColorHexFromRal } from '../ral';
const explosion_state = use_explosion_state() const explosion_state = use_explosion_state()
const globalFenceType = useGlobalFenceType()
const k = 1.5 const k = 1.5
const targetExplosion = { const targetExplosion = {
@ -14,24 +15,67 @@ const targetExplosion = {
stolb: [0 * k, 0 * k, 1 * k], stolb: [0 * k, 0 * k, 1 * k],
verh: [0 * k, 0.25 * k, 0 * k], verh: [0 * k, 0.25 * k, 0 * k],
} }
const { scene: standart_kosynka } = await useGLTF('/models_exp/kosynka.glb')
const { scene: standart_krepleniye_planok } = await useGLTF('/models_exp/krepleniye_planok.glb')
const { scene: standart_osnova_stolba } = await useGLTF('/models_exp/osnova_stolba.glb')
const { scene: standart_planki } = await useGLTF('/models_exp/planki.glb')
const { scene: standart_stolb } = await useGLTF('/models_exp/stolb.glb')
const { scene: standart_verh } = await useGLTF('/models_exp/verh.glb')
const { scene: aristo_kosynka } = await useGLTF('/models_aristo_exp/kosynka.glb')
const { scene: aristo_krepleniye_planok } = await useGLTF('/models_aristo_exp/krepleniye_planok.glb')
const { scene: aristo_osnova_stolba } = await useGLTF('/models_aristo_exp/osnova_stolba.glb')
const { scene: aristo_planki } = await useGLTF('/models_aristo_exp/planki.glb')
const { scene: aristo_stolb } = await useGLTF('/models_aristo_exp/stolb.glb')
const { scene: aristo_verh } = await useGLTF('/models_aristo_exp/verh.glb')
const kosynka = ref()
const krepleniye_planok = ref()
const osnova_stolba = ref()
const planki = ref()
const stolb = ref()
const verh = ref()
const setModels = () => {
const t = globalFenceType.value?.type || 'aristo'
if (t == 'aristo') {
kosynka.value = aristo_kosynka
krepleniye_planok.value = aristo_krepleniye_planok
osnova_stolba.value = aristo_osnova_stolba
planki.value = aristo_planki
stolb.value = aristo_stolb
verh.value = aristo_verh
}
if (t == "standart") {
kosynka.value = standart_kosynka
krepleniye_planok.value = standart_krepleniye_planok
osnova_stolba.value = standart_osnova_stolba
planki.value = standart_planki
stolb.value = standart_stolb
verh.value = standart_verh
}
}
setModels()
watch(() => globalFenceType.value?.type, (t) => {
setModels()
set_material(planki.value, getColorHexFromRal(lamelle_color.value));
[stolb, verh, krepleniye_planok].map(el => set_material(el.value, getColorHexFromRal(pillar_color.value)))
})
const { scene: kosynka } = await useGLTF('/models_exp/kosynka.glb')
const { scene: krepleniye_planok } = await useGLTF('/models_exp/krepleniye_planok.glb')
const { scene: osnova_stolba } = await useGLTF('/models_exp/osnova_stolba.glb')
const { scene: planki } = await useGLTF('/models_exp/planki.glb')
const { scene: stolb } = await useGLTF('/models_exp/stolb.glb')
const { scene: verh } = await useGLTF('/models_exp/verh.glb')
const lamelle_color = use_lamelle_color() const lamelle_color = use_lamelle_color()
const pillar_color = use_pillar_color() const pillar_color = use_pillar_color()
set_material(planki, getColorHexFromRal(lamelle_color.value)); set_material(planki.value, getColorHexFromRal(lamelle_color.value));
[stolb, verh, krepleniye_planok].map(el => set_material(el, getColorHexFromRal(pillar_color.value))) [stolb, verh, krepleniye_planok].map(el => set_material(el.value, getColorHexFromRal(pillar_color.value)))
</script> </script>
<template> <template>
<Suspense> <Suspense>
<TresGroup :position-y="-3.5" :scale="2"> <TresGroup :position-y="!globalFenceType || globalFenceType.type == 'aristo' ? -2 : -3.5" :scale="2"
:key="globalFenceType?.type">
<ModelItem :model="kosynka" :target="explosion_state ? targetExplosion.kosynka : [0, 0, 0]" /> <ModelItem :model="kosynka" :target="explosion_state ? targetExplosion.kosynka : [0, 0, 0]" />
<ModelItem :model="krepleniye_planok" <ModelItem :model="krepleniye_planok"
:target="explosion_state ? targetExplosion.krepleniye_planok : [0, 0, 0]" /> :target="explosion_state ? targetExplosion.krepleniye_planok : [0, 0, 0]" />

View File

@ -16,10 +16,10 @@ renderer.value.toneMappingExposure = 1
renderer.value.shadowMap.enabled = true renderer.value.shadowMap.enabled = true
renderer.value.shadowMap.type = PCFSoftShadowMap renderer.value.shadowMap.type = PCFSoftShadowMap
onMounted(async () => { const pmremGenerator = new PMREMGenerator(renderer.value);
const pmremGenerator = new PMREMGenerator(renderer.value); pmremGenerator.compileEquirectangularShader();
pmremGenerator.compileEquirectangularShader();
onMounted(async () => {
const loader = new GainMapLoader(renderer.value) const loader = new GainMapLoader(renderer.value)
const result = await loader.loadAsync([ const result = await loader.loadAsync([
'hdrmaps/hdr.webp', 'hdrmaps/hdr.webp',
@ -33,17 +33,18 @@ onMounted(async () => {
const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null; const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null;
scene.value.environment = newEnvMap scene.value.environment = newEnvMap
scene.value.environmentIntensity = 1 scene.value.environmentIntensity = 1.25
scene.value.environmentRotation.z = 0.25 scene.value.environmentRotation.z = 0.25
result.renderTarget.texture.dispose(); result.renderTarget.texture.dispose();
})
onBeforeRender(() => { onBeforeRender(() => {
if (camera.value) { if (camera.value) {
const cameraDirection = new Vector3() const cameraDirection = new Vector3()
camera.value.getWorldDirection(cameraDirection); camera.value.getWorldDirection(cameraDirection);
const angle = Math.atan2(cameraDirection.z, cameraDirection.x); const angle = Math.atan2(cameraDirection.z, cameraDirection.x);
scene.value.environmentRotation.z = angle + 0.25 scene.value.environmentRotation.z = angle + 0.25
} }
})
}) })
</script> </script>
<template></template> <template></template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { BufferGeometry, Matrix4, Mesh, Object3D, Vector3 } from 'three'; import { BufferGeometry, Group, Matrix4, Mesh, Object3D, Vector3 } from 'three';
import { getColorHexFromRal } from '../ral'; import { getColorHexFromRal } from '../ral';
const props = defineProps(['index', 'models', 'last_element', 'first_element']) const props = defineProps(['index', 'models', 'last_element', 'first_element'])
@ -16,7 +16,6 @@ const pillar_pattern = use_pattern()
const pillar_topper = use_topper() const pillar_topper = use_topper()
const lamelle_color = use_lamelle_color() const lamelle_color = use_lamelle_color()
const lSize = lamelle_height.value
const pillar_size = 104 * 0.001 const pillar_size = 104 * 0.001
const pillar_one_pos = ref() const pillar_one_pos = ref()
@ -80,7 +79,7 @@ const instanced_fixing_two_el = [
const lamelleMatrix = (i: number) => { const lamelleMatrix = (i: number) => {
const scale_x = (((extra.value as number) || fence_section.value) * 9.9) const scale_x = (((extra.value as number) || fence_section.value) * 9.9)
const pos_x = pillar_size * 0.5 const pos_x = pillar_size * 0.5
const pos_y = (lSize * i) const pos_y = (lamelle_height.value * i)
const pos_z = 0.022 * scale_koef const pos_z = 0.022 * scale_koef
return new Matrix4().fromArray([ return new Matrix4().fromArray([
scale_x, 0, 0, 0, scale_x, 0, 0, 0,
@ -91,7 +90,7 @@ const lamelleMatrix = (i: number) => {
} }
const fixingOneMatrix = (i: number) => { const fixingOneMatrix = (i: number) => {
const pos_x = pillar_one_pos.value + pillar_size * 0.66 const pos_x = pillar_one_pos.value + pillar_size * 0.66
const pos_y = (lSize * i) + 0.01 * scale_koef; const pos_y = (lamelle_height.value * i) + 0.01 * scale_koef;
const pos_z = 0.022 * scale_koef const pos_z = 0.022 * scale_koef
return new Matrix4().fromArray([ return new Matrix4().fromArray([
1, 0, 0, 0, 1, 0, 0, 0,
@ -102,7 +101,7 @@ const fixingOneMatrix = (i: number) => {
} }
const fixingTwoMatrix = (i: number) => { const fixingTwoMatrix = (i: number) => {
const pos_x = pillar_two_pos.value - pillar_size * 0.66 const pos_x = pillar_two_pos.value - pillar_size * 0.66
const pos_y = (lSize * i) + 0.01 * scale_koef; const pos_y = (lamelle_height.value * i) + 0.01 * scale_koef;
const pos_z = 0.022 * scale_koef const pos_z = 0.022 * scale_koef
return new Matrix4().fromArray([ return new Matrix4().fromArray([
1, 0, 0, 0, 1, 0, 0, 0,
@ -130,19 +129,35 @@ const setBraceCount = () => {
setBraceCount() setBraceCount()
watch(lamelles_count, setBraceCount) watch(lamelles_count, setBraceCount)
const calculateAddScale = (model: Mesh, k = 1): number => {
if (!model.geometry.boundingBox) {
model.geometry.computeBoundingBox(); // Вычисляем boundingBox, если он не был вычислен ранее
}
const boundingBoxHeight = Math.abs(
model.geometry.boundingBox!.max.y - model.geometry.boundingBox!.min.y
);
if (boundingBoxHeight === 0) {
console.warn('Bounding box height is zero. Returning default scale of 1.');
return 1; // Возвращаем 1 при делении на ноль
}
return (lamelle_height.value * k) / boundingBoxHeight;
};
const pillar = ref<Mesh[]>([]) const pillar = ref<Mesh[]>([])
const setPillar = () => { const setPillar = () => {
const top = props.models.pillar_top.children[0]; const top = props.models.pillar_top.children[0];
top.position.setComponent(1, lSize * lamelles_count.value + lamelles_count.value * 0.0001 * scale_koef); top.position.setComponent(1, lamelle_height.value * lamelles_count.value * scale_koef);
const pillar_outer = props.models.pillar_center.children[0]; const pillar_outer = props.models.pillar_center.children[0];
pillar_outer.scale.setComponent(1, lamelles_count.value); pillar_outer.scale.setComponent(1, lamelles_count.value * calculateAddScale(pillar_outer));
const pillar_inner = props.models.pillar_inner.children[0]; const pillar_inner = props.models.pillar_inner.children[0];
pillar_inner.scale.setComponent(1, lamelles_count.value); pillar_inner.scale.setComponent(1, lamelles_count.value * calculateAddScale(pillar_inner));
const bottom = props.models.pillar_bottom.children[0]; const bottom = props.models.pillar_bottom.children[0];
bottom.position.setComponent(1, lSize * -0.5);
let arr = [top, pillar_outer, pillar_inner, bottom] let arr = [top, pillar_outer, pillar_inner, bottom]
arr.map(el => { arr.map(el => {
@ -151,48 +166,50 @@ const setPillar = () => {
set_material( set_material(
{ children: [arr[2]] }, { children: [arr[2]] },
getColorHexFromRal(pillar_color.value), getColorHexFromRal(pillar_color.value),
{ pattern: pillar_pattern.value, count: lamelles_count.value }, { pattern: pillar_pattern.value, count: lamelles_count.value * calculateAddScale(pillar_inner) },
true true
) )
pillar.value = arr.map(el => el.clone()) pillar.value = arr.map(el => el.clone())
} }
setPillar() setPillar()
watch([pillar_pattern, pillar_color, pillar_topper, fence_section, lamelles_count], setPillar) watch([pillar_pattern, pillar_color, pillar_topper, fence_section, lamelles_count, lamelle_height], setPillar)
const fastening = ref<Object3D[]>([]) const fastening = ref<Object3D[]>([])
const setFastening = () => { const setFastening = () => {
const top_one = props.models.fixing.clone().children[0]; const top_one = props.models.fixing.clone().children[0];
top_one.position.set( top_one.position.set(
pillar_one_pos.value + pillar_size * 0.66, pillar_one_pos.value + pillar_size * 0.66,
lamelles_count.value * lSize - 0.015 * scale_koef, lamelles_count.value * lamelle_height.value - 0.015 * scale_koef,
0.025 * scale_koef 0.025 * scale_koef
) )
const top_two = props.models.fixing.clone().children[0]; const top_two = props.models.fixing.clone().children[0];
top_two.position.set( top_two.position.set(
pillar_two_pos.value - pillar_size * 0.66, pillar_two_pos.value - pillar_size * 0.66,
lamelles_count.value * lSize - 0.01 * scale_koef, lamelles_count.value * lamelle_height.value - 0.01 * scale_koef,
0.025 * scale_koef 0.025 * scale_koef
) )
const v = ((extra.value as number) || fence_section.value) * 10 const v = ((extra.value as number) || fence_section.value) * 10
const top = props.models.fastening_top.clone().children[0]; const top = props.models.fastening_top.clone().children[0];
top.position.set( top.position.set(
pillar_size * 0.5, pillar_size * 0.5,
lamelles_count.value * lSize - 0.0275 * scale_koef, lamelles_count.value * lamelle_height.value -0.0275 * scale_koef,
0 0
); );
top.scale.setComponent(0, v); top.scale.setComponent(0, v);
let c = 0.0019 * scale_koef
const side_one = props.models.fastening_side.clone().children[0]; const side_one = props.models.fastening_side.clone().children[0];
side_one.name = 'side_one' side_one.name = 'side_one'
side_one.position.set(pillar_one_pos.value, 0, 0.002 * scale_koef); side_one.position.set(pillar_one_pos.value, 0, c);
side_one.scale.set(1, lamelles_count.value, 1) side_one.scale.set(1, lamelles_count.value * calculateAddScale(side_one), 1)
const side_two = props.models.fastening_side.clone().children[0]; const side_two = props.models.fastening_side.clone().children[0];
side_two.name = 'side_two' side_two.name = 'side_two'
side_two.scale.set(-1, lamelles_count.value, -1) side_two.scale.set(-1, lamelles_count.value * calculateAddScale(side_two), -1)
side_two.position.set(pillar_two_pos.value, 0, -0.005 * scale_koef); side_two.position.set(pillar_two_pos.value, 0, c);
let arr = [top_one, top_two, top, side_one, side_two]; let arr = [top_one, top_two, top, side_one, side_two];
[top, side_one, side_two, ...braces.value].map(el => { [top, side_one, side_two, ...braces.value].map(el => {
@ -201,7 +218,7 @@ const setFastening = () => {
fastening.value = arr.map(el => el.clone()) fastening.value = arr.map(el => el.clone())
} }
setFastening() setFastening()
watch([pillar_color, lamelles_count, pillar_one_pos, pillar_two_pos], setFastening) watch([pillar_color, pillar_one_pos, pillar_two_pos, lamelles_count, lamelle_height], setFastening)
const setLamellesColor = () => { const setLamellesColor = () => {
if (instanced_lamelle.value) { if (instanced_lamelle.value) {
@ -214,6 +231,7 @@ watch([instanced_lamelle, lamelle_color], setLamellesColor)
watch([ watch([
instanced_lamelle, instanced_lamelle,
lamelles_count, lamelles_count,
lamelle_height,
fence_section, fence_section,
], () => { ], () => {
const translationVector = new Vector3(0, 20, 20) const translationVector = new Vector3(0, 20, 20)
@ -243,7 +261,7 @@ watch([
}); });
</script> </script>
<template> <template>
<TresGroup :scale="scale_koef" :position-x="translate_to_section" :name="`fence ${index}`" :position-y="lSize * 0.5"> <TresGroup :scale="scale_koef" :position-x="translate_to_section" :name="`fence ${index}`" :position-y="0">
<TresGroup name="pillar_one" v-if="!remove_pillar && show_pillar_one" :position-x="pillar_one_pos"> <TresGroup name="pillar_one" v-if="!remove_pillar && show_pillar_one" :position-x="pillar_one_pos">
<template v-for="item in pillar"> <template v-for="item in pillar">
<TresMesh v-bind="item.clone()" /> <TresMesh v-bind="item.clone()" />

View File

@ -3,7 +3,7 @@ import { Object3D, Vector3 } from 'three';
//@ts-ignore //@ts-ignore
import { useGLTF, } from '@tresjs/cientos' import { useGLTF, } from '@tresjs/cientos'
import type { OrbitControlsProps } from '@tresjs/cientos/dist/core/controls/OrbitControls.vue.js'; import type { OrbitControlsProps } from '@tresjs/cientos/dist/core/controls/OrbitControls.vue.js';
import { getModel, toppers, type toppersIds } from '../topper'; import { getModel, allToppers, baseToppers, aristoToppers, type toppersIds } from '../topper';
const section_count = use_section_count() const section_count = use_section_count()
const extra_section = use_extra_section() const extra_section = use_extra_section()
@ -20,103 +20,156 @@ const goto_target = use_goto_target()
const { scene, controls, camera } = useTresContext() const { scene, controls, camera } = useTresContext()
const { seek, seekAll } = useSeek() const { seek, seekAll } = useSeek()
const topper_models = {} as { [key: toppersIds]: Object3D } const globalFenceType = useGlobalFenceType()
for await (const element of toppers) {
const { scene } = await useGLTF(getModel(element.id))
topper_models[element.id] = scene
}
const { scene: model_pillar_center } = await useGLTF('/models_one/pillar/center.glb') const top = ref(null)
const { scene: model_pillar_bottom } = await useGLTF('/models_one/pillar/bottom.glb') const pillar_top = ref()
const { scene: model_pillar_inner } = await useGLTF('/models_one/pillar/inner.glb') const pillar_center = ref(null)
const { scene: model_pillar_brace } = await useGLTF('/models_one/pillar/brace.glb') const pillar_bottom = ref(null)
const pillar_inner = ref(null)
const pillar_brace = ref(null)
const fastening_top = ref(null)
const fastening_side = ref(null)
const fixing = ref(null)
const lamelle = ref(null)
const { scene: model_fastening_top } = await useGLTF('/models_one/fastening/top.glb'); const total = ref(0)
const { scene: model_fastening_side } = await useGLTF('/models_one/fastening/side.glb'); const size = ref(0)
const { scene: model_fixing } = await useGLTF('/models_one/fixing.glb'); const count = ref(0)
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 top = ref(top_model)
const pillar_top = ref(topper_models[pillar_topper.value])
const pillar_center = ref(model_pillar_center)
const pillar_bottom = ref(model_pillar_bottom)
const pillar_inner = ref(model_pillar_inner)
const pillar_brace = ref(model_pillar_brace)
const fastening_top = ref(model_fastening_top)
const fastening_side = ref(model_fastening_side)
const fixing = ref(model_fixing)
const lamelle = ref(lamelle_model)
watch(pillar_topper, async () => {
pillar_top.value = topper_models[pillar_topper.value]
})
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], () => {
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 { 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 });
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]
}
setPillarTopper()
watch(pillar_topper, setPillarTopper)
total.value = (section_count.value + ~~(!!extra_section.value))
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)
}
onMounted(async () => { await loadAll() })
</script> </script>
<template> <template>
<TresGroup name="base"> <TresGroup name="base">
<template v-for="line in (total >= min_for_square) ? 4 : 1" :key="`${line}_${count}`"> <template v-for="line in (total >= min_for_square) ? 4 : 1"
:key="`${globalFenceType?.id ?? 5}_${line}_${count}`">
<ModelLine :models="{ <ModelLine :models="{
top, top,
pillar_center, pillar_top, pillar_bottom, pillar_inner, pillar_center, pillar_top, pillar_bottom, pillar_inner,

View File

@ -0,0 +1,58 @@
<!-- RenderModeController.vue -->
<script setup lang="ts">
import { watch, onMounted } from 'vue';
import { useTresContext } from '@tresjs/core';
// Пропсы для управления render-mode
const props = defineProps({
renderMode: {
type: String,
default: 'always', // Значение по умолчанию
validator: (value: string) => ['always', 'never'].includes(value),
},
});
// Получаем контекст TresJS
const { renderer, scene, camera } = useTresContext();
// Функция для обновления render-mode
const updateRenderMode = () => {
if (!renderer.value || !scene.value || !camera.value) {
console.warn('Tres context is not ready');
return;
}
console.log(scene)
console.log(renderer)
switch (props.renderMode) {
case 'always':
// Рендерим постоянно
scene.value.visible = true
break;
case 'manual':
// Полностью останавливаем рендеринг
scene.value.visible = false
break;
default:
console.warn(`Unsupported render mode: ${props.renderMode}`);
}
};
// Наблюдаем за изменением renderMode
watch(
() => props.renderMode,
() => {
updateRenderMode();
}
);
// Инициализация при монтировании
onMounted(() => {
updateRenderMode();
});
</script>
<template>
<!-- Компонент не требует шаблона, так как он управляет логикой -->
</template>

57
components/scene.vue Normal file
View File

@ -0,0 +1,57 @@
<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">
<Suspense>
<ModelEnv />
</Suspense>
<Suspense>
<slot />
</Suspense>
</TresCanvas>
</Suspense>
</div>
</template>

View File

@ -1,24 +1,63 @@
export const toppers = [ interface Topper {
id: number; // ID элемента
name: string; // Название
filename: string; // Имя файла
model: string; // Модель
}
export const baseToppers = [
{ name: 'Ровный', filename: 'icon_stolb_verh_3.svg', model: 'top' }, { name: 'Ровный', filename: 'icon_stolb_verh_3.svg', model: 'top' },
{ name: 'Вершина 1', filename: 'icon_stolb_verh_2.svg', model: 'decor1' }, { name: 'Вершина 1', filename: 'icon_stolb_verh_2.svg', model: 'decor1' },
{ name: 'Вершина 2', filename: 'icon_stolb_verh_1.svg', model: 'decor2' }, { name: 'Вершина 2', filename: 'icon_stolb_verh_1.svg', model: 'decor2' },
].map((el, i) => Object.assign(el, { id: i })) ].map((el, i) => Object.assign(el, { id: i }))
export const getFilename = (id: toppersIds) => { export const aristoToppers = [
const el = toppers.find(el => el.id == id) { name: 'Колпак', filename: 'icon_stolb_verh_1.svg', model: 'aristo1' },
].map((el, i) => Object.assign(el, { id: i }))
export const allToppers = {
'baseToppers': baseToppers,
'aristoToppers': aristoToppers,
} as { [key: string]: Topper[] }
function getToppersByType(type: string | null): any {
if (!type) type = 'NONE'
// Проверяем, существует ли указанный тип в объекте allToppers
if (allToppers[type]) {
return allToppers[type];
}
// Если типа нет, берем первый доступный элемент из allToppers
const keys = Object.keys(allToppers);
if (keys.length > 0) {
const firstKey = keys[0];
return allToppers[firstKey];
}
// Если объект allToppers пустой, возвращаем значение по умолчанию (например, пустой массив)
return [];
}
export const getFilename = (id: toppersIds, type: string | null) => {
let toppers = getToppersByType(type)
const el = toppers.find((el: Topper) => el.id == id)
if (!el || !el.filename) return undefined if (!el || !el.filename) return undefined
return `/topper/${el?.filename}` return `/topper/${el?.filename}`
} }
export const getName = (id: toppersIds) => { export const getName = (id: toppersIds, type: string | null) => {
const el = toppers.find(el => el.id == id) let toppers = getToppersByType(type)
const el = toppers.find((el: Topper) => el.id == id)
if (!el || !el.filename) return undefined if (!el || !el.filename) return undefined
return el.name return el.name
} }
export const getModel = (id: toppersIds) => { export const getModel = (id: toppersIds, type: string | null) => {
const el = toppers.find(el => el.id == id) let toppers = getToppersByType(type)
let el = toppers.find((el: Topper) => el.id == id)
if (!el || !el.filename) return undefined if (!el || !el.filename) return undefined
return `/models_one/pillar/topper/${el?.model}.glb` return `/models_one/pillar/topper/${el?.model}.glb`
} }
export type toppersIds = typeof toppers[number]['id'] export type toppersIds = typeof allToppers[string][number]['id']

View File

@ -0,0 +1,7 @@
interface calc {
id: number
type: 'standart' | 'aristo'
calc: ApiCalcType
}
export const useGlobalFenceType = () => useState<calc | null>('fence-global-type', () => null)

View File

@ -0,0 +1,57 @@
// useSceneVisibility.ts
import { ref, onMounted, onBeforeUnmount } from 'vue';
export function useSceneVisibility() {
const isIntersecting = ref(false); // Состояние видимости
let observer: IntersectionObserver | null = null;
let debounceTimeout: ReturnType<typeof setTimeout> | null = null;
const startObserver = (element: HTMLElement) => {
observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (debounceTimeout) {
clearTimeout(debounceTimeout); // Очищаем предыдущий таймер
}
// Устанавливаем новый таймер
debounceTimeout = setTimeout(() => {
isIntersecting.value = entry.isIntersecting;
}, 100); // Задержка в 300 мс
});
},
{ threshold: 0.1 } // Порог видимости
);
if (element) {
observer.observe(element);
}
};
const stopObserver = () => {
if (observer) {
observer.disconnect();
observer = null;
}
if (debounceTimeout) {
clearTimeout(debounceTimeout); // Очищаем таймер при остановке наблюдателя
debounceTimeout = null;
}
};
onMounted(() => {
// Инициализация наблюдателя при монтировании
});
onBeforeUnmount(() => {
// Очистка наблюдателя при размонтировании
stopObserver();
});
return {
isIntersecting,
startObserver,
stopObserver,
};
}

View File

@ -32,7 +32,7 @@ if(route.path !== '/404') {
<h1>Вы ищете страницу, которой не существует. Вернитесь на главную страницу сайта</h1> <h1>Вы ищете страницу, которой не существует. Вернитесь на главную страницу сайта</h1>
<p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с <p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с
покупкой забора здесь.</p> покупкой забора здесь.</p>
<p class="hidden"> <p>
<code> <code>
{{ error?.message }} {{ error?.message }}
</code> </code>

View File

@ -10,6 +10,9 @@ export default defineNuxtConfig({
htmlAttrs: { htmlAttrs: {
lang: 'ru', lang: 'ru',
}, },
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/tabler--fence-filled.svg' },
]
}, },
}, },
ssr: true, ssr: true,
@ -25,7 +28,9 @@ export default defineNuxtConfig({
], ],
runtimeConfig: { runtimeConfig: {
public: { public: {
apiBase: '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" : "https://mns.kustarshina.ru/kp",
// apiBase: 'http://localhost:8000',
imgBase: 'https://mns.kustarshina.ru', imgBase: 'https://mns.kustarshina.ru',
baseUrl: '', baseUrl: '',
yandexMetrika: { yandexMetrika: {
@ -46,9 +51,9 @@ export default defineNuxtConfig({
vite: { vite: {
assetsInclude: ['**/*.glb', '**/*.gltf'], assetsInclude: ['**/*.glb', '**/*.gltf'],
build: { build: {
target: 'esnext' target: 'esnext',
// minify: 'esbuild' // minify: 'esbuild'
// minify: false minify: false,
}, },
}, },
robots: { robots: {

View File

@ -7,6 +7,8 @@ import { marked } from 'marked';
import og_img from '/og_img.png' import og_img from '/og_img.png'
const globalFenceType = useGlobalFenceType()
const { data: seoData } = await apiFetch<ApiKpType>(`kp/1/`) const { data: seoData } = await apiFetch<ApiKpType>(`kp/1/`)
useSeoMeta({ useSeoMeta({
title: seoData.value?.title, title: seoData.value?.title,
@ -21,7 +23,10 @@ const { data: menuData } = await apiFetch<ApiMenuType>(`menu/1/?ordering=order`)
const pagesData = menuData.value ? menuData.value.pages : [] const pagesData = menuData.value ? menuData.value.pages : []
const { data: reviewsData } = await apiFetch<ApiReviewsType[]>(`review/`) const { data: reviewsData } = await apiFetch<ApiReviewsType[]>(`review/`)
const { data: calculatorData } = await apiFetch(`calculator/5/`)
const { data: calculators } = await apiFetch<ApiCalcType[]>('calculator/')
const openTab = ref<number>()
if (calculators.value?.length) openTab.value = calculators.value[0].id
const about = pagesData.find(el => el.slug == 'about') const about = pagesData.find(el => el.slug == 'about')
const reviews = pagesData.find(el => el.slug == 'clients') const reviews = pagesData.find(el => el.slug == 'clients')
@ -37,16 +42,32 @@ const aboutText = computed(() => marked.parse(about?.content || ''))
const deliveryText = computed(() => delivery?.content.split('[col]').map(el => marked.parse(el || ''))) const deliveryText = computed(() => delivery?.content.split('[col]').map(el => marked.parse(el || '')))
const advantagesText = computed(() => { const advantagesText = computed(() => {
let c = advantages?.content || '' let c = advantages?.content || ''
Object.entries(calculatorData.value || {}).map(item => { Object.entries(calculators.value?.find(el => el.id == openTab.value) || {}).map(item => {
c = c.replaceAll(`[${item[0]}]`, roubleSign.format(item[1])) c = c.replaceAll(`[${item[0]}]`, roubleSign.format(item[1] as number))
}) })
return marked.parse(c) return marked.parse(c)
}) })
const { data: advData } = await apiFetch<ApiAdvantageType[]>(`advantage/`) const { data: advData } = await apiFetch<ApiAdvantageType[]>(`advantage/`)
watch(openTab, () => {
if (!openTab.value) return
if (!calculators.value) return
let type = 'standart'
if (openTab.value == 6) {
type = 'aristo'
}
const v = {
id: openTab.value as number,
type: type as "standart" | "aristo",
calc: calculators.value.find(el => el.id == openTab.value) as ApiCalcType,
}
globalFenceType.value = v
})
</script> </script>
<template> <template>
<div> <div>
<Modal :calcData="(calculators || []).find(el => el.id == openTab)" />
<div class="siteblock bg-white" :id="about?.slug" v-if="about"> <div class="siteblock bg-white" :id="about?.slug" v-if="about">
<div class="container"> <div class="container">
<h1 class="siteblock-title">{{ about?.title }}</h1> <h1 class="siteblock-title">{{ about?.title }}</h1>
@ -92,16 +113,36 @@ const { data: advData } = await apiFetch<ApiAdvantageType[]>(`advantage/`)
</div> </div>
</div> </div>
<div class="siteblock bg-white" :id="advantages?.slug"> <div class="siteblock bg-white" :id="advantages?.slug">
<div class="flex col-span-full gap-2 justify-center">
<template v-for="item in calculators">
<div class="flex items-center gap-2">
<label class="rounded p-2 cursor-pointer flex gap-2" :for="item.id.toString()">
<input type="radio" :id="item.id.toString()" :value="item.id" v-model="openTab"
:checked="openTab == item.id">
<span :class="[
'text-2xl font-bold bg-clip-text text-transparent drop-shadow-xs',
openTab == item.id
? 'bg-gradient-to-r from-yellow-400 via-orange-500 to-red-600'
: 'bg-gradient-to-r from-slate-400 to-slate-600'
]">
{{ item.title }}
</span>
</label>
</div>
</template>
</div>
<div class="container gap-4"> <div class="container gap-4">
<div class="col-span-full xl:col-span-8"> <div class="col-span-full xl:col-span-8">
<ExpDiagram /> <Suspense>
<ExpDiagram />
</Suspense>
</div> </div>
<div class="col-span-full xl:col-span-4 prose"> <div class="col-span-full xl:col-span-4 prose">
<span v-html="advantagesText"></span> <span v-html="advantagesText"></span>
</div> </div>
</div> </div>
</div> </div>
<div class="siteblock siteblock_imgbg bg-slate-500" <div class="siteblock siteblock_imgbg bg-slate-500 "
:style="[{ backgroundImage: `url(${[imgBase, delivery?.image].join('/')})` }]"> :style="[{ backgroundImage: `url(${[imgBase, delivery?.image].join('/')})` }]">
<NuxtImg :src="[imgBase, delivery?.image].join('/')" class="invisible" v-if="delivery" <NuxtImg :src="[imgBase, delivery?.image].join('/')" class="invisible" v-if="delivery"
alt="коричневый забор" title="" format="webp" loading="lazy" /> alt="коричневый забор" title="" format="webp" loading="lazy" />

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/models_exp.zip Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M19 17v3a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1v-3zm-8 0v3a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-3zm9-5a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zM8.707 3.293l2 2A1 1 0 0 1 11 6v5H5V6a1 1 0 0 1 .293-.707l2-2a1 1 0 0 1 1.414 0m8 0l2 2A1 1 0 0 1 19 6v5h-6V6a1 1 0 0 1 .293-.707l2-2a1 1 0 0 1 1.414 0"/></svg>

After

Width:  |  Height:  |  Size: 424 B

View File

@ -3,6 +3,7 @@ export async function apiFetch<T>(path: string) {
const apiBase = config.public.apiBase const apiBase = config.public.apiBase
const headers = new Headers() const headers = new Headers()
headers.set('Referer', config.public.baseUrl) headers.set('Referer', config.public.baseUrl)
return useFetch<T>(`${apiBase}/${path}`, { return useFetch<T>(`${apiBase}/${path}`, {
baseURL: config.public.baseUrl, baseURL: config.public.baseUrl,
headers, headers,

View File

@ -1,38 +0,0 @@
import { ShadowMaterial, type WebGLProgramParameters } from "three";
const gaussMaterial = new ShadowMaterial()
gaussMaterial.onBeforeCompile = (shader: WebGLProgramParameters) => {
// Изменяем фрагментный шейдер
console.log(shader.fragmentShader)
shader.fragmentShader = `
float gaussian(float x, float sigma) {
return (1.0 / (sqrt(2.0 * 3.14159265) * sigma)) * exp(-0.5 * (x * x) / (sigma * sigma));
}
${shader.fragmentShader}`
.replace(
`#include <colorspace_fragment>`,
`#include <colorspace_fragment>
// Add Gaussian blur logic here
vec4 blurColor = vec4(0.0);
vec4 test = vec4(0.0);
float totalWeight = 0.0;
const int blurRadius = 1; // Adjust for more or less blur
for (int x = -blurRadius; x <= blurRadius; x++) {
for (int y = -blurRadius; y <= blurRadius; y++) {
// Используем gaussian для вычисления веса
float weight = gaussian(float(sqrt(float(x * x + y * y))), float(blurRadius));
// Получаем цвет текстуры с учетом смещения
blurColor += texture2D(pointShadowMap[0], gl_FragCoord.xy + vec2(float(x), float(y))) * weight;
totalWeight += weight;
}
}
blurColor /= totalWeight;
gl_FragColor = vec4(texture2D(pointShadowMap[0], vec2(-1.0,1.0)));
`);
}
export default gaussMaterial