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 />
|
||||
<NuxtPage />
|
||||
<Footer />
|
||||
<Modal />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { DoubleSide, PointLight } from 'three';
|
||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||
import { TresCanvas } from '@tresjs/core'
|
||||
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({
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
|
@ -12,17 +12,29 @@ const controlsState = reactive({
|
|||
enableZoom: false,
|
||||
minPolarAngle: degToRad(60),
|
||||
maxPolarAngle: degToRad(100),
|
||||
// minAzimuthAngle: degToRad(0),
|
||||
// maxAzimuthAngle: degToRad(180),
|
||||
//minAzimuthAngle: degToRad(0),
|
||||
//maxAzimuthAngle: degToRad(180),
|
||||
})
|
||||
const cameraStat = reactive({
|
||||
position: [0, 0, 5],
|
||||
aspect: 1920 / 600,
|
||||
// fov: 40,
|
||||
})
|
||||
const renderMode = computed(() => (isIntersecting.value ? 'always' : 'manual'));
|
||||
|
||||
onMounted(async () => {
|
||||
if (container.value) {
|
||||
await nextTick()
|
||||
startObserver(container.value);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopObserver();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="calc">
|
||||
<div class="calc" ref="container">
|
||||
<ClientOnly fallback-tag="div">
|
||||
<template #fallback>
|
||||
<div class="fallback">
|
||||
|
@ -30,14 +42,14 @@ const cameraStat = reactive({
|
|||
</div>
|
||||
</template>
|
||||
<Loader />
|
||||
<TresCanvas clear-color="#e2e8f0">
|
||||
<TresCanvas clear-color="#e2e8f0" :render-mode="renderMode" :key="renderMode">
|
||||
<TresPerspectiveCamera v-bind="cameraStat" ref="camera" />
|
||||
<OrbitControls v-bind="controlsState" make-default />
|
||||
<Suspense>
|
||||
<ModelSmoothCamera />
|
||||
<ModelEnv />
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<ModelEnv />
|
||||
<ModelSmoothCamera />
|
||||
</Suspense>
|
||||
<TresGroup>
|
||||
<Suspense>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Vector3 } from 'three';
|
||||
import { getColorNameFromRal } from '@/components/ral'
|
||||
import type { ralTypes } from '@/components/ral'
|
||||
|
||||
import { predefLamelleColors, predefPillarColors } from '~/composables/useCalc';
|
||||
|
@ -18,6 +19,7 @@ const section_count = use_section_count()
|
|||
const extra_section = use_extra_section()
|
||||
const total_length = use_total_length()
|
||||
const min_length = use_min_length()
|
||||
const globalFenceType = useGlobalFenceType()
|
||||
|
||||
if (!pillar_color.value) {
|
||||
const r = Math.floor(Math.random() * predefPillarColors.length)
|
||||
|
@ -204,7 +206,8 @@ const calc_table = computed(() => {
|
|||
:goto_cam="new Vector3(1, 2, -1)" />
|
||||
<label for="pillar_topper">Колпак столба</label>
|
||||
<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_cam="new Vector3(-1, 2, 1)" />
|
||||
</div>
|
||||
|
|
|
@ -1,24 +1,39 @@
|
|||
<script setup lang="ts">
|
||||
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']);
|
||||
type pType = {
|
||||
list: typeof patterns | typeof toppers,
|
||||
list: typeof patterns | typeof baseToppers,
|
||||
func: any
|
||||
type: string | null
|
||||
}
|
||||
const p = {
|
||||
const p = reactive({
|
||||
list: [],
|
||||
type: null,
|
||||
func: () => { }
|
||||
} as pType
|
||||
} as pType)
|
||||
|
||||
if (props.patterns == 'pattern') {
|
||||
p.list = patterns;
|
||||
p.func = getPattern
|
||||
} else if (props.patterns == 'topper') {
|
||||
p.list = toppers
|
||||
p.list = baseToppers
|
||||
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>
|
||||
<template>
|
||||
<template v-for="item in p.list">
|
||||
|
|
|
@ -3,6 +3,9 @@ import { TresCanvas } from '@tresjs/core'
|
|||
import { OrbitControls } from '@tresjs/cientos'
|
||||
import { Vector3 } from 'three';
|
||||
|
||||
const container = ref<HTMLElement | null>(null);
|
||||
const { isIntersecting, startObserver, stopObserver } = useSceneVisibility();
|
||||
|
||||
const camera = ref()
|
||||
const controls = ref()
|
||||
const controlsState = reactive({
|
||||
|
@ -17,22 +20,6 @@ const explosion_state = use_explosion_state()
|
|||
const toggleExpState = () => {
|
||||
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) => {
|
||||
if (camera.value && controls.value) {
|
||||
|
@ -41,42 +28,58 @@ const changeDistance = (v = 1) => {
|
|||
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>
|
||||
<template>
|
||||
<div class="h-96 relative">
|
||||
<ClientOnly fallback-tag="div">
|
||||
<template #fallback>
|
||||
<div class="fallback">
|
||||
Загрузка 3D модели
|
||||
</div>
|
||||
</template>
|
||||
<TresCanvas height="600">
|
||||
<TresPerspectiveCamera :position="[-7, 2, 4]" ref="camera" />
|
||||
<OrbitControls v-bind="controlsState" ref="controls" make-default />
|
||||
<ModelEnv />
|
||||
<Suspense>
|
||||
<ModelDiagram />
|
||||
</Suspense>
|
||||
</TresCanvas>
|
||||
</ClientOnly>
|
||||
<div class="canvas-icons">
|
||||
<a href="#" @click.prevent="toggleExpState">
|
||||
<Icon name="mdi:arrow-collapse-horizontal" v-if="explosion_state" />
|
||||
<Icon name="mdi:arrow-expand-horizontal" v-else />
|
||||
</a>
|
||||
<a href="#" @click.prevent="changeDistance(-0.5)"
|
||||
:class="[{ 'disabled': camera ? (camera.position.distanceTo(new Vector3(0, 0, 0)) <= controlsState.minDistance) : null }]">
|
||||
<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>
|
||||
<Suspense>
|
||||
<div ref="container" class="h-96 relative">
|
||||
<ClientOnly fallback-tag="div">
|
||||
<template #fallback>
|
||||
<div class="fallback">
|
||||
Загрузка 3D модели
|
||||
</div>
|
||||
</template>
|
||||
{{ renderMode }}
|
||||
<TresCanvas height="600">
|
||||
<ModelUpdateRenderMode :render-mode="renderMode" />
|
||||
<TresPerspectiveCamera :position="[-7, 2, 4]" ref="camera" />
|
||||
<OrbitControls v-bind="controlsState" ref="controls" make-default />
|
||||
<Suspense>
|
||||
<ModelEnv />
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<ModelDiagram />
|
||||
</Suspense>
|
||||
</TresCanvas>
|
||||
</ClientOnly>
|
||||
<div class="canvas-icons">
|
||||
<a href="#" @click.prevent="toggleExpState">
|
||||
<Icon name="mdi:arrow-collapse-horizontal" v-if="explosion_state" />
|
||||
<Icon name="mdi:arrow-expand-horizontal" v-else />
|
||||
</a>
|
||||
<a href="#" @click.prevent="changeDistance(-0.5)"
|
||||
:class="[{ 'disabled': camera ? (camera.position.distanceTo(new Vector3(0, 0, 0)) <= controlsState.minDistance) : null }]">
|
||||
<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>
|
||||
</Suspense>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { getName as getTopperName } from './topper';
|
|||
|
||||
const config = useRuntimeConfig()
|
||||
const apiBase = config.public.apiBase
|
||||
const { data: calculatorData } = await apiFetch<ApiCalcType>(`calculator/5/`)
|
||||
const props = defineProps(['calcData'])
|
||||
const isModalOpen = useState('modal_open', () => false)
|
||||
|
||||
const lamelle_height = useState<number>('lamelle_height')
|
||||
|
@ -122,12 +122,14 @@ const total_colors = computed(() => {
|
|||
`Столбы ${pillar_color.value} ${getColorNameFromRal(pillar_color.value)}`,
|
||||
]
|
||||
})
|
||||
|
||||
const total_txt = computed(() => {
|
||||
if (!calculatorData.value) return
|
||||
const pillar = parseFloat(calculatorData.value.pillar)
|
||||
const pillar_base = parseFloat(calculatorData.value.pillar_base)
|
||||
const lamelles_block = parseFloat(calculatorData.value.lamelles_block)
|
||||
const { discount } = calculatorData.value
|
||||
const calculatorData = props.calcData as ApiCalcType
|
||||
if (!calculatorData) return
|
||||
const pillar = parseFloat(calculatorData.pillar)
|
||||
const pillar_base = parseFloat(calculatorData.pillar_base)
|
||||
const lamelles_block = parseFloat(calculatorData.lamelles_block)
|
||||
const { discount } = calculatorData
|
||||
|
||||
const sections = section_count.value as number
|
||||
const extra_m = extra_section.value as number * 0.001
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useGLTF } from '@tresjs/cientos'
|
|||
import { getColorHexFromRal } from '../ral';
|
||||
|
||||
const explosion_state = use_explosion_state()
|
||||
const globalFenceType = useGlobalFenceType()
|
||||
|
||||
const k = 1.5
|
||||
const targetExplosion = {
|
||||
|
@ -14,24 +15,67 @@ const targetExplosion = {
|
|||
stolb: [0 * k, 0 * k, 1 * 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 pillar_color = use_pillar_color()
|
||||
|
||||
set_material(planki, getColorHexFromRal(lamelle_color.value));
|
||||
[stolb, verh, krepleniye_planok].map(el => set_material(el, getColorHexFromRal(pillar_color.value)))
|
||||
set_material(planki.value, getColorHexFromRal(lamelle_color.value));
|
||||
[stolb, verh, krepleniye_planok].map(el => set_material(el.value, getColorHexFromRal(pillar_color.value)))
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<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="krepleniye_planok"
|
||||
: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.type = PCFSoftShadowMap
|
||||
|
||||
onMounted(async () => {
|
||||
const pmremGenerator = new PMREMGenerator(renderer.value);
|
||||
pmremGenerator.compileEquirectangularShader();
|
||||
const pmremGenerator = new PMREMGenerator(renderer.value);
|
||||
pmremGenerator.compileEquirectangularShader();
|
||||
|
||||
onMounted(async () => {
|
||||
const loader = new GainMapLoader(renderer.value)
|
||||
const result = await loader.loadAsync([
|
||||
'hdrmaps/hdr.webp',
|
||||
|
@ -33,17 +33,18 @@ onMounted(async () => {
|
|||
const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null;
|
||||
|
||||
scene.value.environment = newEnvMap
|
||||
scene.value.environmentIntensity = 1
|
||||
scene.value.environmentIntensity = 1.25
|
||||
scene.value.environmentRotation.z = 0.25
|
||||
result.renderTarget.texture.dispose();
|
||||
})
|
||||
onBeforeRender(() => {
|
||||
if (camera.value) {
|
||||
const cameraDirection = new Vector3()
|
||||
camera.value.getWorldDirection(cameraDirection);
|
||||
const angle = Math.atan2(cameraDirection.z, cameraDirection.x);
|
||||
scene.value.environmentRotation.z = angle + 0.25
|
||||
}
|
||||
|
||||
onBeforeRender(() => {
|
||||
if (camera.value) {
|
||||
const cameraDirection = new Vector3()
|
||||
camera.value.getWorldDirection(cameraDirection);
|
||||
const angle = Math.atan2(cameraDirection.z, cameraDirection.x);
|
||||
scene.value.environmentRotation.z = angle + 0.25
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<template></template>
|
|
@ -1,5 +1,5 @@
|
|||
<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';
|
||||
|
||||
const props = defineProps(['index', 'models', 'last_element', 'first_element'])
|
||||
|
@ -16,7 +16,6 @@ const pillar_pattern = use_pattern()
|
|||
const pillar_topper = use_topper()
|
||||
const lamelle_color = use_lamelle_color()
|
||||
|
||||
const lSize = lamelle_height.value
|
||||
const pillar_size = 104 * 0.001
|
||||
|
||||
const pillar_one_pos = ref()
|
||||
|
@ -80,7 +79,7 @@ const instanced_fixing_two_el = [
|
|||
const lamelleMatrix = (i: number) => {
|
||||
const scale_x = (((extra.value as number) || fence_section.value) * 9.9)
|
||||
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
|
||||
return new Matrix4().fromArray([
|
||||
scale_x, 0, 0, 0,
|
||||
|
@ -91,7 +90,7 @@ const lamelleMatrix = (i: number) => {
|
|||
}
|
||||
const fixingOneMatrix = (i: number) => {
|
||||
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
|
||||
return new Matrix4().fromArray([
|
||||
1, 0, 0, 0,
|
||||
|
@ -102,7 +101,7 @@ const fixingOneMatrix = (i: number) => {
|
|||
}
|
||||
const fixingTwoMatrix = (i: number) => {
|
||||
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
|
||||
return new Matrix4().fromArray([
|
||||
1, 0, 0, 0,
|
||||
|
@ -130,19 +129,35 @@ const setBraceCount = () => {
|
|||
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 setPillar = () => {
|
||||
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];
|
||||
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];
|
||||
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];
|
||||
bottom.position.setComponent(1, lSize * -0.5);
|
||||
|
||||
let arr = [top, pillar_outer, pillar_inner, bottom]
|
||||
arr.map(el => {
|
||||
|
@ -151,48 +166,50 @@ const setPillar = () => {
|
|||
set_material(
|
||||
{ children: [arr[2]] },
|
||||
getColorHexFromRal(pillar_color.value),
|
||||
{ pattern: pillar_pattern.value, count: lamelles_count.value },
|
||||
{ pattern: pillar_pattern.value, count: lamelles_count.value * calculateAddScale(pillar_inner) },
|
||||
true
|
||||
)
|
||||
pillar.value = arr.map(el => el.clone())
|
||||
}
|
||||
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 setFastening = () => {
|
||||
const top_one = props.models.fixing.clone().children[0];
|
||||
top_one.position.set(
|
||||
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
|
||||
)
|
||||
|
||||
const top_two = props.models.fixing.clone().children[0];
|
||||
top_two.position.set(
|
||||
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
|
||||
)
|
||||
|
||||
const v = ((extra.value as number) || fence_section.value) * 10
|
||||
|
||||
const top = props.models.fastening_top.clone().children[0];
|
||||
top.position.set(
|
||||
pillar_size * 0.5,
|
||||
lamelles_count.value * lSize - 0.0275 * scale_koef,
|
||||
lamelles_count.value * lamelle_height.value -0.0275 * scale_koef,
|
||||
0
|
||||
);
|
||||
top.scale.setComponent(0, v);
|
||||
|
||||
let c = 0.0019 * scale_koef
|
||||
const side_one = props.models.fastening_side.clone().children[0];
|
||||
side_one.name = 'side_one'
|
||||
side_one.position.set(pillar_one_pos.value, 0, 0.002 * scale_koef);
|
||||
side_one.scale.set(1, lamelles_count.value, 1)
|
||||
side_one.position.set(pillar_one_pos.value, 0, c);
|
||||
side_one.scale.set(1, lamelles_count.value * calculateAddScale(side_one), 1)
|
||||
|
||||
const side_two = props.models.fastening_side.clone().children[0];
|
||||
side_two.name = 'side_two'
|
||||
side_two.scale.set(-1, lamelles_count.value, -1)
|
||||
side_two.position.set(pillar_two_pos.value, 0, -0.005 * scale_koef);
|
||||
side_two.scale.set(-1, lamelles_count.value * calculateAddScale(side_two), -1)
|
||||
side_two.position.set(pillar_two_pos.value, 0, c);
|
||||
|
||||
let arr = [top_one, top_two, top, side_one, side_two];
|
||||
[top, side_one, side_two, ...braces.value].map(el => {
|
||||
|
@ -201,7 +218,7 @@ const setFastening = () => {
|
|||
fastening.value = arr.map(el => el.clone())
|
||||
}
|
||||
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 = () => {
|
||||
if (instanced_lamelle.value) {
|
||||
|
@ -214,6 +231,7 @@ watch([instanced_lamelle, lamelle_color], setLamellesColor)
|
|||
watch([
|
||||
instanced_lamelle,
|
||||
lamelles_count,
|
||||
lamelle_height,
|
||||
fence_section,
|
||||
], () => {
|
||||
const translationVector = new Vector3(0, 20, 20)
|
||||
|
@ -243,7 +261,7 @@ watch([
|
|||
});
|
||||
</script>
|
||||
<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">
|
||||
<template v-for="item in pillar">
|
||||
<TresMesh v-bind="item.clone()" />
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Object3D, Vector3 } from 'three';
|
|||
//@ts-ignore
|
||||
import { useGLTF, } from '@tresjs/cientos'
|
||||
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 extra_section = use_extra_section()
|
||||
|
@ -20,103 +20,156 @@ const goto_target = use_goto_target()
|
|||
const { scene, controls, camera } = useTresContext()
|
||||
const { seek, seekAll } = useSeek()
|
||||
|
||||
const topper_models = {} as { [key: toppersIds]: Object3D }
|
||||
for await (const element of toppers) {
|
||||
const { scene } = await useGLTF(getModel(element.id))
|
||||
topper_models[element.id] = scene
|
||||
}
|
||||
const globalFenceType = useGlobalFenceType()
|
||||
|
||||
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 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 { 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 total = ref(0)
|
||||
const size = ref(0)
|
||||
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;
|
||||
|
||||
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>
|
||||
<template>
|
||||
<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="{
|
||||
top,
|
||||
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: 'Вершина 1', filename: 'icon_stolb_verh_2.svg', model: 'decor1' },
|
||||
{ name: 'Вершина 2', filename: 'icon_stolb_verh_1.svg', model: 'decor2' },
|
||||
].map((el, i) => Object.assign(el, { id: i }))
|
||||
|
||||
export const getFilename = (id: toppersIds) => {
|
||||
const el = toppers.find(el => el.id == id)
|
||||
export const aristoToppers = [
|
||||
{ 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
|
||||
return `/topper/${el?.filename}`
|
||||
}
|
||||
|
||||
export const getName = (id: toppersIds) => {
|
||||
const el = toppers.find(el => el.id == id)
|
||||
export const getName = (id: toppersIds, type: string | null) => {
|
||||
let toppers = getToppersByType(type)
|
||||
|
||||
const el = toppers.find((el: Topper) => el.id == id)
|
||||
if (!el || !el.filename) return undefined
|
||||
return el.name
|
||||
}
|
||||
export const getModel = (id: toppersIds) => {
|
||||
const el = toppers.find(el => el.id == id)
|
||||
export const getModel = (id: toppersIds, type: string | null) => {
|
||||
let toppers = getToppersByType(type)
|
||||
|
||||
let el = toppers.find((el: Topper) => el.id == id)
|
||||
if (!el || !el.filename) return undefined
|
||||
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>
|
||||
<p>Извините, но мы не можем найти запрашиваемую страницу. К сожалению, мы не можем помочь вам с
|
||||
покупкой забора здесь.</p>
|
||||
<p class="hidden">
|
||||
<p>
|
||||
<code>
|
||||
{{ error?.message }}
|
||||
</code>
|
||||
|
|
|
@ -10,6 +10,9 @@ export default defineNuxtConfig({
|
|||
htmlAttrs: {
|
||||
lang: 'ru',
|
||||
},
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/tabler--fence-filled.svg' },
|
||||
]
|
||||
},
|
||||
},
|
||||
ssr: true,
|
||||
|
@ -25,7 +28,9 @@ export default defineNuxtConfig({
|
|||
],
|
||||
runtimeConfig: {
|
||||
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',
|
||||
baseUrl: '',
|
||||
yandexMetrika: {
|
||||
|
@ -46,9 +51,9 @@ export default defineNuxtConfig({
|
|||
vite: {
|
||||
assetsInclude: ['**/*.glb', '**/*.gltf'],
|
||||
build: {
|
||||
target: 'esnext'
|
||||
target: 'esnext',
|
||||
// minify: 'esbuild'
|
||||
// minify: false
|
||||
minify: false,
|
||||
},
|
||||
},
|
||||
robots: {
|
||||
|
|
|
@ -7,6 +7,8 @@ import { marked } from 'marked';
|
|||
|
||||
import og_img from '/og_img.png'
|
||||
|
||||
const globalFenceType = useGlobalFenceType()
|
||||
|
||||
const { data: seoData } = await apiFetch<ApiKpType>(`kp/1/`)
|
||||
useSeoMeta({
|
||||
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 { 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 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 advantagesText = computed(() => {
|
||||
let c = advantages?.content || ''
|
||||
Object.entries(calculatorData.value || {}).map(item => {
|
||||
c = c.replaceAll(`[${item[0]}]`, roubleSign.format(item[1]))
|
||||
Object.entries(calculators.value?.find(el => el.id == openTab.value) || {}).map(item => {
|
||||
c = c.replaceAll(`[${item[0]}]`, roubleSign.format(item[1] as number))
|
||||
})
|
||||
return marked.parse(c)
|
||||
})
|
||||
|
||||
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>
|
||||
<template>
|
||||
<div>
|
||||
<Modal :calcData="(calculators || []).find(el => el.id == openTab)" />
|
||||
<div class="siteblock bg-white" :id="about?.slug" v-if="about">
|
||||
<div class="container">
|
||||
<h1 class="siteblock-title">{{ about?.title }}</h1>
|
||||
|
@ -92,16 +113,36 @@ const { data: advData } = await apiFetch<ApiAdvantageType[]>(`advantage/`)
|
|||
</div>
|
||||
</div>
|
||||
<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="col-span-full xl:col-span-8">
|
||||
<ExpDiagram />
|
||||
<Suspense>
|
||||
<ExpDiagram />
|
||||
</Suspense>
|
||||
</div>
|
||||
<div class="col-span-full xl:col-span-4 prose">
|
||||
<span v-html="advantagesText"></span>
|
||||
</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('/')})` }]">
|
||||
<NuxtImg :src="[imgBase, delivery?.image].join('/')" class="invisible" v-if="delivery"
|
||||
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.
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 headers = new Headers()
|
||||
headers.set('Referer', config.public.baseUrl)
|
||||
|
||||
return useFetch<T>(`${apiBase}/${path}`, {
|
||||
baseURL: config.public.baseUrl,
|
||||
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