Compare commits
40 Commits
bx-1480-ca
...
main
Author | SHA1 | Date |
---|---|---|
|
1cf4c40878 | |
|
50f61e9341 | |
|
9cad1c4103 | |
|
01cb9440b2 | |
|
88b3651415 | |
|
27c35b5a32 | |
|
655f2f6ec4 | |
|
21ae73f0f6 | |
|
fea664c209 | |
|
5c48f41fa6 | |
|
04fa4e6ba8 | |
|
e3814b150c | |
|
92535f6f17 | |
|
ee2a26907c | |
|
c471e49418 | |
|
052f4d6e41 | |
|
d10e3392e7 | |
|
5f571fb244 | |
|
1bd900ec4c | |
|
c9d1e2d02b | |
|
799eab7dee | |
|
e063154a1b | |
|
c1a83a2efc | |
|
1c74647a21 | |
|
9bac0c3ff7 | |
|
ac24ec9b5d | |
|
85fcc911f4 | |
|
77a4ac2baf | |
|
a15aaa9a1a | |
|
3b4a79061a | |
|
bff2219193 | |
|
dc1e491d1f | |
|
79ee614db8 | |
|
40ab9d82db | |
|
786642bcc0 | |
|
98a4bcbf06 | |
|
a0a38a9d25 | |
|
5b9d288a57 | |
|
e6ad912e85 | |
|
b90035130c |
1
app.vue
1
app.vue
|
@ -6,6 +6,5 @@ import '@/assets/main.scss'
|
||||||
<Header />
|
<Header />
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
<Footer />
|
<Footer />
|
||||||
<Modal />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -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 },
|
||||||
|
@ -20,9 +20,21 @@ const cameraStat = reactive({
|
||||||
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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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,22 +28,37 @@ 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>
|
||||||
|
<div ref="container" class="h-96 relative">
|
||||||
<ClientOnly fallback-tag="div">
|
<ClientOnly fallback-tag="div">
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<div class="fallback">
|
<div class="fallback">
|
||||||
Загрузка 3D модели
|
Загрузка 3D модели
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
{{ renderMode }}
|
||||||
<TresCanvas height="600">
|
<TresCanvas height="600">
|
||||||
|
<ModelUpdateRenderMode :render-mode="renderMode" />
|
||||||
<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 />
|
||||||
|
<Suspense>
|
||||||
<ModelEnv />
|
<ModelEnv />
|
||||||
|
</Suspense>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ModelDiagram />
|
<ModelDiagram />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
@ -77,6 +79,7 @@ onMounted(() => {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Suspense>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]" />
|
||||||
|
|
|
@ -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,10 +33,10 @@ 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()
|
||||||
|
@ -45,5 +45,6 @@ onBeforeRender(() => {
|
||||||
scene.value.environmentRotation.z = angle + 0.25
|
scene.value.environmentRotation.z = angle + 0.25
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<template></template>
|
<template></template>
|
|
@ -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()" />
|
||||||
|
|
|
@ -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,10 +20,36 @@ 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()
|
||||||
|
|
||||||
|
const top = ref(null)
|
||||||
|
const pillar_top = ref()
|
||||||
|
const pillar_center = ref(null)
|
||||||
|
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 total = ref(0)
|
||||||
|
const size = ref(0)
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
const min_for_square = 12;
|
||||||
|
|
||||||
|
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) {
|
for await (const element of toppers) {
|
||||||
const { scene } = await useGLTF(getModel(element.id))
|
const { scene } = await useGLTF(getModel(element.id, key))
|
||||||
topper_models[element.id] = scene
|
topper_models[key][element.id] = scene
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { scene: model_pillar_center } = await useGLTF('/models_one/pillar/center.glb')
|
const { scene: model_pillar_center } = await useGLTF('/models_one/pillar/center.glb')
|
||||||
|
@ -36,26 +62,34 @@ const { scene: model_fastening_side } = await useGLTF('/models_one/fastening/sid
|
||||||
const { scene: model_fixing } = await useGLTF('/models_one/fixing.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: 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 } = await useGLTF('/models_one/lamel_100.glb', { draco: true });
|
||||||
|
|
||||||
const top = ref(top_model)
|
const { scene: lamelle_model_standart } = await useGLTF('/models_one/lamel_100.glb', { draco: true });
|
||||||
const pillar_top = ref(topper_models[pillar_topper.value])
|
const { scene: lamelle_model_aristo } = await useGLTF('/models_one/lamel_100_aristo.glb', { draco: true });
|
||||||
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 () => {
|
top.value = top_model
|
||||||
pillar_top.value = topper_models[pillar_topper.value]
|
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 total = ref((section_count.value + ~~(!!extra_section.value)))
|
const setPillarTopper = () => {
|
||||||
const size = ref(Math.ceil(total.value / 4))
|
let key = 'baseToppers'
|
||||||
const count = ref((total.value >= 4) ? size.value : total.value)
|
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], () => {
|
watch(() => [section_count.value, extra_section.value], () => {
|
||||||
total.value = (section_count.value + ~~(!!extra_section.value))
|
total.value = (section_count.value + ~~(!!extra_section.value))
|
||||||
|
@ -100,7 +134,7 @@ const setTarget = (smooth = false) => {
|
||||||
goto_cam.value = new Vector3(f, f, f)
|
goto_cam.value = new Vector3(f, f, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
watch([lamelles_count, fence_section], () => {
|
watch([lamelles_count, fence_section, lamelle_height], () => {
|
||||||
setTarget()
|
setTarget()
|
||||||
open_calc.value = []
|
open_calc.value = []
|
||||||
})
|
})
|
||||||
|
@ -110,13 +144,32 @@ watch(open_calc, () => {
|
||||||
setTarget(true)
|
setTarget(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const min_for_square = 12;
|
|
||||||
|
|
||||||
setTarget()
|
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,
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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']
|
|
@ -0,0 +1,7 @@
|
||||||
|
interface calc {
|
||||||
|
id: number
|
||||||
|
type: 'standart' | 'aristo'
|
||||||
|
calc: ApiCalcType
|
||||||
|
}
|
||||||
|
export const useGlobalFenceType = () => useState<calc | null>('fence-global-type', () => null)
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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,9 +113,29 @@ 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">
|
||||||
|
<Suspense>
|
||||||
<ExpDiagram />
|
<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>
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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 |
|
@ -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,
|
||||||
|
|
|
@ -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
|
|
Loading…
Reference in New Issue