144
assets/main.scss
|
@ -178,10 +178,18 @@ a[href^="#"] {
|
|||
}
|
||||
|
||||
&_calc {
|
||||
@apply py-0;
|
||||
// @apply;
|
||||
|
||||
&-canvas {
|
||||
@apply relative h-[50vh] min-h-[600px];
|
||||
.container {
|
||||
@apply gap-4 items-stretch;
|
||||
|
||||
>* {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
>div:first-child {
|
||||
@apply min-h-[600px];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,13 +281,17 @@ label {
|
|||
}
|
||||
|
||||
input {
|
||||
@apply bg-neutral-200 border border-gray-300 text-gray-900 rounded focus:ring-blue-500 focus:border-blue-500 text-lg p-2.5 disabled:cursor-not-allowed disabled:text-black;
|
||||
@apply bg-white border border-gray-300 text-gray-900 text-lg p-2.5 rounded focus:ring-blue-500 focus:border-blue-500 focus-visible:border-blue-500 disabled:cursor-not-allowed disabled:text-black
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
@apply w-4 h-4;
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
@apply w-full bg-transparent border-transparent cursor-pointer appearance-none disabled:opacity-50 disabled:pointer-events-none focus:outline-none [&::-webkit-slider-thumb]:w-2.5 [&::-webkit-slider-thumb]:h-2.5 [&::-webkit-slider-thumb]:-mt-0.5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-[0_0_0_4px_#111] [&::-webkit-slider-thumb]:shadow-slate-500 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:transition-all [&::-webkit-slider-thumb]:duration-150 [&::-webkit-slider-thumb]:ease-in-out [&::-webkit-slider-thumb]:dark:bg-neutral-700 [&::-moz-range-thumb]:w-2.5 [&::-moz-range-thumb]:h-2.5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:border-4 [&::-moz-range-thumb]:border-blue-600 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:transition-all [&::-moz-range-thumb]:duration-150 [&::-moz-range-thumb]:ease-in-out [&::-webkit-slider-runnable-track]:w-full [&::-webkit-slider-runnable-track]:h-2 [&::-webkit-slider-runnable-track]:bg-gray-100 [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:dark:bg-neutral-700 [&::-moz-range-track]:w-full [&::-moz-range-track]:h-2 [&::-moz-range-track]:bg-gray-100 [&::-moz-range-track]:rounded-full;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@apply block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 max-w-full min-h-10 max-h-40;
|
||||
}
|
||||
|
@ -294,51 +306,133 @@ button {
|
|||
}
|
||||
|
||||
.form {
|
||||
@apply col-span-full grid grid-cols-12 gap-4;
|
||||
@apply col-span-full h-full flex flex-col gap-4 justify-between;
|
||||
|
||||
&-group {
|
||||
@apply rounded flex flex-col gap-4 p-4 shadow shadow-slate-400;
|
||||
}
|
||||
|
||||
&-row {
|
||||
@apply col-span-full flex flex-row flex-wrap gap-4;
|
||||
@apply flex flex-row flex-wrap gap-2;
|
||||
|
||||
&_picker {
|
||||
@apply grid grid-cols-2 justify-center items-center;
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
@apply flex flex-row gap-4 items-center justify-start xl:justify-center flex-wrap xl:flex-nowrap;
|
||||
@apply flex flex-row items-center justify-start flex-wrap;
|
||||
|
||||
label {
|
||||
@apply w-full xl:w-auto;
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@apply hover:text-primary cursor-pointer transition-colors text-2xl;
|
||||
@apply hover:text-primary cursor-pointer transition-colors text-xl;
|
||||
|
||||
&.disabled {
|
||||
@apply text-neutral pointer-events-none cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&_checkbox {
|
||||
@apply w-full xl:w-auto flex-row xl:flex-initial flex-nowrap
|
||||
&_total {
|
||||
@apply w-full flex-nowrap;
|
||||
|
||||
input {
|
||||
@apply w-20 bg-slate-100;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
@apply min-w-[calc(100%-8rem)] xl:min-w-min;
|
||||
&_checkbox {
|
||||
@apply w-full xl:w-auto flex-row xl:flex-initial flex-nowrap gap-2 items-center;
|
||||
|
||||
label {
|
||||
@apply leading-none;
|
||||
}
|
||||
|
||||
svg {
|
||||
@apply text-slate-500;
|
||||
}
|
||||
}
|
||||
|
||||
&_range {
|
||||
@apply w-full;
|
||||
|
||||
>div {
|
||||
@apply flex flex-nowrap w-full gap-2 items-center;
|
||||
|
||||
input {
|
||||
@apply flex-grow;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply min-w-16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&_picker {
|
||||
@apply w-full;
|
||||
|
||||
svg {
|
||||
@apply text-slate-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.color_picker {
|
||||
@apply leading-none;
|
||||
.picker {
|
||||
@apply leading-none w-full row-start-2;
|
||||
|
||||
&_open {
|
||||
@apply flex-wrap;
|
||||
}
|
||||
|
||||
&_disabled {
|
||||
@apply pointer-events-none opacity-50;
|
||||
}
|
||||
|
||||
&-input {
|
||||
@apply flex items-center justify-between;
|
||||
|
||||
>div {
|
||||
@apply min-h-12 overflow-hidden rounded border-gray-300 shadow flex leading-none grow justify-center items-center p-2;
|
||||
|
||||
span {
|
||||
@apply grow text-left max-w-[80%] break-words line-clamp-2;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&-button {
|
||||
@apply min-w-6 shrink-0 h-full;
|
||||
}
|
||||
|
||||
&-selected {
|
||||
@apply size-10 rounded border-gray-300 shadow inline-block leading-none;
|
||||
|
||||
&__active {
|
||||
@apply outline outline-2 outline-offset-2 outline-primary;
|
||||
}
|
||||
@apply h-10 overflow-hidden rounded border-gray-300 shadow inline-block leading-none grow;
|
||||
}
|
||||
|
||||
&-changer {
|
||||
@apply absolute w-80 z-10 p-4 border rounded bg-white flex gap-0 right-0 lg:right-auto;
|
||||
@apply gap-3 mt-4 max-h-36 overflow-auto order-10 hidden;
|
||||
|
||||
&_open {
|
||||
@apply flex flex-wrap col-span-2;
|
||||
}
|
||||
}
|
||||
|
||||
&-list {
|
||||
@apply flex gap-4 w-full;
|
||||
}
|
||||
|
||||
&-item {
|
||||
@apply size-10;
|
||||
|
||||
&--empty {
|
||||
@apply block bg-slate-300;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.feature {
|
||||
|
@ -369,11 +463,15 @@ button {
|
|||
}
|
||||
}
|
||||
|
||||
.calc {
|
||||
@apply max-h-[800px] relative h-full;
|
||||
}
|
||||
|
||||
.calc_table {
|
||||
@apply flex flex-col gap-2 mb-4;
|
||||
@apply flex flex-col gap-2 self-end;
|
||||
|
||||
>.grid {
|
||||
@apply gap-2 items-center;
|
||||
@apply gap-2 items-end;
|
||||
|
||||
>[class*=col] {
|
||||
@apply p-1 border-solid border-b;
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<script setup lang="ts">
|
||||
import { TresCanvas } from '@tresjs/core'
|
||||
import { Stats, OrbitControls } from '@tresjs/cientos'
|
||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||
|
||||
const controlsState = reactive({
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
enablePan: false,
|
||||
enableZoom: false,
|
||||
minPolarAngle: degToRad(60),
|
||||
maxPolarAngle: degToRad(100),
|
||||
minAzimuthAngle: degToRad(0),
|
||||
maxAzimuthAngle: degToRad(180),
|
||||
})
|
||||
const cameraStat = reactive({
|
||||
position: [0, 0, 5],
|
||||
aspect: 1920 / 600,
|
||||
// fov: 40,
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="calc">
|
||||
<ClientOnly fallback-tag="div">
|
||||
<template #fallback>
|
||||
<div class="fallback">
|
||||
Загрузка 3D модели
|
||||
</div>
|
||||
</template>
|
||||
<Loader />
|
||||
<TresCanvas clear-color="#e2e8f0">
|
||||
<TresPerspectiveCamera v-bind="cameraStat" ref="camera" />
|
||||
<OrbitControls v-bind="controlsState" make-default />
|
||||
<Suspense>
|
||||
<ModelSmoothCamera />
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<ModelEnv />
|
||||
</Suspense>
|
||||
<TresGroup>
|
||||
<Suspense>
|
||||
<ModelParametric />
|
||||
</Suspense>
|
||||
</TresGroup>
|
||||
</TresCanvas>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,281 @@
|
|||
<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';
|
||||
import { getName as getPattern, type patternIds } from '../pattern';
|
||||
import { getName as getTopper, type toppersIds } from '../topper';
|
||||
|
||||
const lamelle_height = use_lamelle_height()
|
||||
const lamelles_count = use_lamelles_count()
|
||||
const fence_section = use_fence_section()
|
||||
const remove_pillar = use_remove_pillar()
|
||||
const pillar_color = use_pillar_color()
|
||||
const pillar_pattern = use_pattern()
|
||||
const pillar_topper = use_topper()
|
||||
const lamelle_color = use_lamelle_color()
|
||||
const section_count = use_section_count()
|
||||
const extra_section = use_extra_section()
|
||||
const total_length = use_total_length()
|
||||
const min_length = use_min_length()
|
||||
|
||||
if (!pillar_color.value) {
|
||||
const r = Math.floor(Math.random() * predefPillarColors.length)
|
||||
pillar_color.value = predefPillarColors[r] as ralTypes
|
||||
lamelle_color.value = predefLamelleColors[r] as ralTypes
|
||||
}
|
||||
|
||||
const parametric = reactive({
|
||||
length: {
|
||||
min: min_length.value,
|
||||
max: 2470,
|
||||
step: 1,
|
||||
},
|
||||
total_length: {
|
||||
min: min_length.value * 0.001,
|
||||
max: undefined,
|
||||
step: 0.5,
|
||||
},
|
||||
height: {
|
||||
min: 20 + lamelle_height.value * 1000 * 5,
|
||||
max: 20 + lamelle_height.value * 1000 * 20,
|
||||
step: lamelle_height.value * 1000,
|
||||
}
|
||||
})
|
||||
const form_state = reactive({
|
||||
length: fence_section.value * 1000,
|
||||
fence_length: 104,
|
||||
height: 100 + lamelles_count.value * parametric.height.step,
|
||||
total_length: total_length.value,
|
||||
total_length_mm: fence_section.value * 1000,
|
||||
full_sections: section_count.value,
|
||||
extra_section: 0,
|
||||
auto_length: true,
|
||||
remove_pillar: false
|
||||
})
|
||||
let copy_form_state = Object.assign({}, form_state)
|
||||
const form_refs = {
|
||||
length: ref(),
|
||||
height: ref(),
|
||||
total_length: ref(),
|
||||
}
|
||||
|
||||
const changeParametres = () => {
|
||||
// console.log('form', form_state.total_length * 1000, 'copy', copy_form_state.total_length * 1000)
|
||||
|
||||
const lamelles = Math.floor(form_state.height / parametric.height.step)
|
||||
|
||||
for (const key in form_state) {
|
||||
if (parametric.hasOwnProperty(key)) {
|
||||
const key_p = key as keyof typeof parametric
|
||||
const key_s = key as keyof typeof form_state
|
||||
if (parametric[key_p].max) {
|
||||
if ((form_state[key_s] as number) > parametric[key_p].max) {
|
||||
(form_state[key_s] as number) = parametric[key_p].max
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form_state.total_length_mm = form_state.total_length * 1000
|
||||
|
||||
let { fence_length, total_length_mm, auto_length, length, full_sections } = form_state
|
||||
length = parseFloat(length.toString())
|
||||
if (total_length_mm < parametric.length.min) total_length_mm = parametric.length.min
|
||||
|
||||
if (auto_length) {
|
||||
let w = parametric.length.min
|
||||
const max_sections = Math.floor((total_length_mm - fence_length) / (parametric.length.min + fence_length))
|
||||
const min_sections = Math.floor((total_length_mm - fence_length) / (parametric.length.max + fence_length)) || 1
|
||||
|
||||
for (let index = min_sections; index <= max_sections; index++) {
|
||||
full_sections = index
|
||||
w = (total_length_mm - fence_length - fence_length * index) / index
|
||||
if (
|
||||
w >= parametric.length.min
|
||||
&& w <= parametric.length.max
|
||||
&& w * index <= total_length_mm
|
||||
) {
|
||||
break
|
||||
}
|
||||
}
|
||||
length = w
|
||||
} else {
|
||||
full_sections = Math.floor((total_length_mm - fence_length) / (length + fence_length))
|
||||
if (full_sections == 0) {
|
||||
length = total_length_mm - fence_length - fence_length
|
||||
}
|
||||
}
|
||||
const calc_full_section = () => (full_sections * length) + (full_sections * fence_length) + fence_length
|
||||
let total_length_calc = calc_full_section()
|
||||
if (
|
||||
Math.round(total_length_mm - total_length_calc) > 0
|
||||
&& Math.round(total_length_mm - total_length_calc) <= fence_length
|
||||
) {
|
||||
full_sections -= 1
|
||||
total_length_calc = calc_full_section()
|
||||
}
|
||||
if (Math.round(total_length_mm - total_length_calc) > 0) {
|
||||
form_state.extra_section = total_length_mm - total_length_calc - fence_length
|
||||
} else {
|
||||
form_state.extra_section = 0
|
||||
}
|
||||
|
||||
form_state.full_sections = full_sections
|
||||
form_state.length = typeof length == 'string' ? parseFloat(length) : length
|
||||
form_state.fence_length = typeof fence_length == 'string' ? parseFloat(fence_length) : fence_length
|
||||
|
||||
copy_form_state = Object.assign({}, form_state)
|
||||
|
||||
total_length.value = form_state.total_length
|
||||
lamelles_count.value = lamelles
|
||||
fence_section.value = form_state.length * 0.001
|
||||
section_count.value = form_state.full_sections
|
||||
extra_section.value = form_state.extra_section
|
||||
remove_pillar.value = form_state.remove_pillar
|
||||
|
||||
goal('calc_fence', form_state)
|
||||
}
|
||||
|
||||
const setLamelleColor = (color: ralTypes) => {
|
||||
lamelle_color.value = color;
|
||||
}
|
||||
const setPillarColor = (color: ralTypes) => {
|
||||
pillar_color.value = color
|
||||
}
|
||||
const setPillarPattern = (id: patternIds) => {
|
||||
pillar_pattern.value = id
|
||||
}
|
||||
const setPillarTopper = (id: toppersIds) => {
|
||||
pillar_topper.value = id
|
||||
}
|
||||
watch(() => form_state, changeParametres, { deep: true })
|
||||
|
||||
const isModalOpen = useState('modal_open', () => false)
|
||||
const toggleModal = () => {
|
||||
isModalOpen.value = !isModalOpen.value
|
||||
}
|
||||
const goal = (target: string, params: object) => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
if (nuxtApp.$metrika) {
|
||||
(nuxtApp.$metrika as any).reachGoal(target, params || {})
|
||||
}
|
||||
}
|
||||
const calc_table = computed(() => {
|
||||
return [
|
||||
{
|
||||
name: 'Секции',
|
||||
value: section_count,
|
||||
extra: 1,
|
||||
extra_name: 'Дополнительная секция'
|
||||
},
|
||||
{
|
||||
name:
|
||||
`Ламели`,
|
||||
value: section_count.value * lamelles_count.value,
|
||||
extra: 1 * lamelles_count.value
|
||||
},
|
||||
{
|
||||
name: `Столбы`,
|
||||
value: !form_state.remove_pillar ? section_count.value + ~~(!!form_state.extra_section) + 1 : `—`,
|
||||
extra: `—`
|
||||
},
|
||||
]
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="form">
|
||||
<div class="form-group">
|
||||
<div class="form-row form-row_picker">
|
||||
<label for="lamelle_color">Цвет ламелей</label>
|
||||
<DropdownPicker type="color" :cb="setLamelleColor" name="lamelle_color" :color="lamelle_color"
|
||||
:goto_target="new Vector3(0, lamelles_count * lamelle_height * 0.75, 0)"
|
||||
:goto_cam="new Vector3(0.75, 0.75, 0.75)" />
|
||||
<label for="pillar_color">Цвет столба</label>
|
||||
<DropdownPicker type="color" :cb="setPillarColor" name="pillar_color" :color="pillar_color"
|
||||
:goto_target="new Vector3(-fence_section * 0.5, lamelles_count * lamelle_height, 0)"
|
||||
:goto_cam="new Vector3(-1, -1, 1)" />
|
||||
</div>
|
||||
<div class="form-row form-row_picker">
|
||||
<label for="pillar_pattern">Узор столба</label>
|
||||
<DropdownPicker type="pattern" :cb="setPillarPattern" name="pillar_pattern"
|
||||
:pattern="getPattern(pillar_pattern)" :disabled="remove_pillar"
|
||||
:goto_target="new Vector3(fence_section * 0.5, lamelles_count * lamelle_height, 0)"
|
||||
: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"
|
||||
:goto_target="new Vector3(fence_section * -0.5, lamelles_count * lamelle_height, 0)"
|
||||
:goto_cam="new Vector3(-1, 2, 1)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-row">
|
||||
<div class="form-item form-item_range w-full">
|
||||
<label for="length">Длина ламельного блока, мм</label>
|
||||
<div>
|
||||
<input id="length" type="range" v-bind="parametric.length" v-model="form_state.length"
|
||||
:disabled="form_state.auto_length" :ref="form_refs.length" />
|
||||
<span>{{ form_state.length.toFixed(0) }} мм</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-item form-item_range w-full">
|
||||
<label for="height">Высота забора, мм</label>
|
||||
<div>
|
||||
<input id="height" type="range" v-bind="parametric.height" v-model="form_state.height"
|
||||
:ref="form_refs.height" />
|
||||
<span>{{ form_state.height }} мм</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-item form-item_total">
|
||||
<label for="total_length">Общая длина забора, м</label>
|
||||
<input type="number" id="total_length" v-bind="parametric.total_length" min=0 max="600"
|
||||
v-model="form_state.total_length" :ref="form_refs.total_length" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-item form-item_checkbox">
|
||||
<Icon :name="`mdi:check-box-outline${!form_state.auto_length ? '-blank' : ''}`"
|
||||
@click="form_state.auto_length = !form_state.auto_length" />
|
||||
<input id="auto_length" type="checkbox" hidden v-model="form_state.auto_length" />
|
||||
<label for="auto_length">Автоматический подбор секции</label>
|
||||
</div>
|
||||
<p v-if="!form_state.auto_length" class="text-ioprim text-sm">
|
||||
Рекомендуем вам включить автоподбор длины секции
|
||||
</p>
|
||||
<div class="form-item form-item_checkbox">
|
||||
<Icon :name="`mdi:check-box-outline${!form_state.remove_pillar ? '-blank' : ''}`"
|
||||
@click="form_state.remove_pillar = !form_state.remove_pillar" />
|
||||
<input id="remove_pillar" type="checkbox" hidden v-model="form_state.remove_pillar" />
|
||||
<label for="remove_pillar">Без столбов</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<p v-if="form_state.total_length * 1000 < (parametric.length.min) || form_state.full_sections == 0"
|
||||
class="text-ioprim text-sm">
|
||||
Выбранный размер забора слишком мал для расчета стоимости. Пожалуйста, выберите больший
|
||||
размер, чтобы продолжить.
|
||||
</p>
|
||||
<template v-else>
|
||||
<div class="grid calc_table w-full">
|
||||
<div class="grid grid-cols-4 relative">
|
||||
<template v-for="item in calc_table">
|
||||
<div class="col-span-3 calc_table-maincell">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="col-span-1 calc_table-maincell">{{ item.value }}</div>
|
||||
</template>
|
||||
</div>
|
||||
<button @click.prevent="toggleModal">Рассчитать</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,83 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { FrontSide, RepeatWrapping } from 'three';
|
||||
import { TresCanvas, useTexture } from '@tresjs/core'
|
||||
import { OrbitControls, useGLTF } from '@tresjs/cientos'
|
||||
|
||||
const fence_section = use_fence_section()
|
||||
const section_count = use_section_count()
|
||||
const extra_section = use_extra_section()
|
||||
const max_size = use_max_size()
|
||||
|
||||
const controlsState = reactive({
|
||||
distance: section_count.value,
|
||||
minDistance: 7,
|
||||
maxDistance: 20,
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
enablePan: false,
|
||||
maxPolarAngle: (Math.PI / 2) - 0.2,
|
||||
})
|
||||
const cameraStat = reactive({
|
||||
position: [-4, 2, 8],
|
||||
aspect: 1920 / 600,
|
||||
fov: 40,
|
||||
})
|
||||
|
||||
const pointLight = ref()
|
||||
const loadAll = async () => {
|
||||
const { scene: light } = await useGLTF('/models_light/zabor_so_svetom.glb')
|
||||
pointLight.value = light.children[2]
|
||||
pointLight.value.color = '#f0dea9'
|
||||
pointLight.value.intensity = pointLight.value.intensity * 0.1
|
||||
pointLight.value.shadow.camera.near = 50
|
||||
pointLight.value.shadow.bias = -0.002
|
||||
const j = 4
|
||||
pointLight.value.shadow.mapSize.x = 512 * j
|
||||
pointLight.value.shadow.mapSize.y = 512 * j
|
||||
|
||||
const k = 3
|
||||
pointLight.value.position.x = pointLight.value.position.x * k
|
||||
pointLight.value.position.y = pointLight.value.position.y * k
|
||||
pointLight.value.position.z = pointLight.value.position.z * k
|
||||
}
|
||||
const camera = ref("camera")
|
||||
|
||||
onMounted(() => {
|
||||
cameraStat.aspect = window.innerWidth / (window.innerHeight * 0.5)
|
||||
loadAll()
|
||||
})
|
||||
watch([section_count, extra_section], () => {
|
||||
let v = (section_count.value + ~~(!!extra_section.value)) * 2
|
||||
if (v < controlsState.minDistance) v = controlsState.minDistance;
|
||||
if (v > controlsState.maxDistance) v = controlsState.maxDistance;
|
||||
(camera.value as any).position.normalize().multiplyScalar(v)
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="container min-w-full siteblock_calc-canvas">
|
||||
<ClientOnly fallback-tag="div">
|
||||
<template #fallback>
|
||||
<div class="fallback">
|
||||
Загрузка 3D модели
|
||||
</div>
|
||||
</template>
|
||||
<Loader />
|
||||
<TresCanvas>
|
||||
<TresPerspectiveCamera v-bind="cameraStat" ref="camera" />
|
||||
<OrbitControls v-bind="controlsState" make-default />
|
||||
<TresGroup :position-x="Math.min(section_count, max_size) * fence_section * -1" :position-y="-3">
|
||||
<Suspense>
|
||||
<ModelParametric />
|
||||
</Suspense>
|
||||
</TresGroup>
|
||||
<TresMesh receive-shadow :position-y="-3.65" name="ground">
|
||||
<TresCircleGeometry :args="[50, 32]" :rotate-x="-Math.PI * 0.5" />
|
||||
<TresShadowMaterial :opacity="0.2" />
|
||||
</TresMesh>
|
||||
|
||||
<template v-if="pointLight">
|
||||
<TresPointLight v-bind="pointLight" cast-shadow />
|
||||
</template>
|
||||
</TresCanvas>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
|
@ -1,256 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { getColorNameFromRal } from '@/components/ral'
|
||||
import type { ralTypes } from '@/components/ral'
|
||||
|
||||
import { predefLamelleColors, predefPillarColors } from '~/composables/useCalc';
|
||||
|
||||
const lamelle_height = use_lamelle_height()
|
||||
const lamelles_count = use_lamelles_count()
|
||||
const fence_section = use_fence_section()
|
||||
const remove_pillar = use_remove_pillar()
|
||||
const pillar_color = use_pillar_color()
|
||||
const lamelle_color = use_lamelle_color()
|
||||
const section_count = use_section_count()
|
||||
const extra_section = use_extra_section()
|
||||
const total_length = use_total_length()
|
||||
const min_length = use_min_length()
|
||||
|
||||
if (!pillar_color.value) {
|
||||
const r = Math.floor(Math.random() * predefPillarColors.length)
|
||||
pillar_color.value = predefPillarColors[r] as ralTypes
|
||||
lamelle_color.value = predefLamelleColors[r] as ralTypes
|
||||
}
|
||||
|
||||
const parametric = reactive({
|
||||
length: {
|
||||
min: min_length.value,
|
||||
max: 2470,
|
||||
step: 1,
|
||||
},
|
||||
total_length: {
|
||||
min: min_length.value * 0.001,
|
||||
max: undefined,
|
||||
step: 0.5,
|
||||
},
|
||||
height: {
|
||||
min: 20 + lamelle_height.value * 1000 * 5,
|
||||
max: 20 + lamelle_height.value * 1000 * 20,
|
||||
step: lamelle_height.value * 1000,
|
||||
}
|
||||
})
|
||||
const form_state = reactive({
|
||||
length: fence_section.value * 1000,
|
||||
fence_length: 104,
|
||||
height: 100 + lamelles_count.value * parametric.height.step,
|
||||
total_length: total_length.value,
|
||||
total_length_mm: fence_section.value * 1000,
|
||||
full_sections: section_count.value,
|
||||
extra_section: 0,
|
||||
auto_length: true,
|
||||
remove_pillar: false
|
||||
})
|
||||
let copy_form_state = Object.assign({}, form_state)
|
||||
const form_refs = {
|
||||
length: ref(),
|
||||
height: ref(),
|
||||
total_length: ref(),
|
||||
}
|
||||
|
||||
const changeParametres = () => {
|
||||
// console.log('form', form_state.total_length * 1000, 'copy', copy_form_state.total_length * 1000)
|
||||
|
||||
const lamelles = Math.floor(form_state.height / parametric.height.step)
|
||||
|
||||
for (const key in form_state) {
|
||||
if (parametric.hasOwnProperty(key)) {
|
||||
const key_p = key as keyof typeof parametric
|
||||
const key_s = key as keyof typeof form_state
|
||||
if (parametric[key_p].max) {
|
||||
if ((form_state[key_s] as number) > parametric[key_p].max) {
|
||||
(form_state[key_s] as number) = parametric[key_p].max
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form_state.total_length_mm = form_state.total_length * 1000
|
||||
|
||||
let { fence_length, total_length_mm, auto_length, length, full_sections } = form_state
|
||||
length = parseFloat(length.toString())
|
||||
if (total_length_mm < parametric.length.min) total_length_mm = parametric.length.min
|
||||
|
||||
if (auto_length) {
|
||||
let w = parametric.length.min
|
||||
const max_sections = Math.floor((total_length_mm - fence_length) / (parametric.length.min + fence_length))
|
||||
const min_sections = Math.floor((total_length_mm - fence_length) / (parametric.length.max + fence_length)) || 1
|
||||
|
||||
for (let index = min_sections; index <= max_sections; index++) {
|
||||
full_sections = index
|
||||
w = (total_length_mm - fence_length - fence_length * index) / index
|
||||
if (
|
||||
w >= parametric.length.min
|
||||
&& w <= parametric.length.max
|
||||
&& w * index <= total_length_mm
|
||||
) {
|
||||
break
|
||||
}
|
||||
}
|
||||
length = w
|
||||
} else {
|
||||
full_sections = Math.floor((total_length_mm - fence_length) / (length + fence_length))
|
||||
if (full_sections == 0) {
|
||||
length = total_length_mm - fence_length - fence_length
|
||||
}
|
||||
}
|
||||
const calc_full_section = () => (full_sections * length) + (full_sections * fence_length) + fence_length
|
||||
let total_length_calc = calc_full_section()
|
||||
if (Math.round(total_length_mm - total_length_calc) > 0 && Math.round(total_length_mm - total_length_calc) <= fence_length) {
|
||||
full_sections -= 1
|
||||
total_length_calc = calc_full_section()
|
||||
}
|
||||
if (Math.round(total_length_mm - total_length_calc) > 0) {
|
||||
form_state.extra_section = total_length_mm - total_length_calc - fence_length
|
||||
} else {
|
||||
form_state.extra_section = 0
|
||||
}
|
||||
|
||||
form_state.full_sections = full_sections
|
||||
form_state.length = typeof length == 'string' ? parseFloat(length) : length
|
||||
form_state.fence_length = typeof fence_length == 'string' ? parseFloat(fence_length) : fence_length
|
||||
|
||||
copy_form_state = Object.assign({}, form_state)
|
||||
|
||||
total_length.value = form_state.total_length
|
||||
lamelles_count.value = lamelles
|
||||
fence_section.value = form_state.length * 0.001
|
||||
section_count.value = form_state.full_sections
|
||||
extra_section.value = form_state.extra_section
|
||||
remove_pillar.value = form_state.remove_pillar
|
||||
|
||||
goal('calc_fence', form_state)
|
||||
}
|
||||
|
||||
const setLamelleColor = (color: ralTypes) => {
|
||||
lamelle_color.value = color
|
||||
}
|
||||
const setPillarColor = (color: ralTypes) => {
|
||||
pillar_color.value = color
|
||||
}
|
||||
watch(() => form_state, changeParametres, { deep: true })
|
||||
|
||||
const isModalOpen = useState('modal_open', () => false)
|
||||
const toggleModal = () => {
|
||||
isModalOpen.value = !isModalOpen.value
|
||||
}
|
||||
const goal = (target: string, params: object) => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
if (nuxtApp.$metrika) {
|
||||
(nuxtApp.$metrika as any).reachGoal(target, params || {})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="container relative py-4">
|
||||
<form class="form">
|
||||
<div class="col-span-12 sm:col-span-6">
|
||||
<div class="form-row">
|
||||
<div class="form-item w-full">
|
||||
<label for="length">Длина ламельного блока, мм</label>
|
||||
<input disabled :value.input="`${form_state.length.toFixed(0)} мм`" class="w-28" />
|
||||
<input id="length" type="range" class="xl:w-full" v-bind="parametric.length"
|
||||
v-model="form_state.length" :disabled="form_state.auto_length" :ref="form_refs.length" />
|
||||
</div>
|
||||
<div class="form-item w-full">
|
||||
<label for="height">Высота забора, мм</label>
|
||||
<input disabled :value="`${form_state.height} мм`" class="w-28" />
|
||||
<input id="height" type="range" class="xl:w-full" v-bind="parametric.height"
|
||||
v-model="form_state.height" :ref="form_refs.height" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 sm:col-span-6">
|
||||
<div class="form-row">
|
||||
<div class="form-item">
|
||||
<label for="lamelle_color">Цвет ламелей</label>
|
||||
<input id="lamelle_color" type="text" :value="getColorNameFromRal(lamelle_color)" class="w-60"
|
||||
disabled />
|
||||
<ColorPicker :cb="setLamelleColor" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="pillar_color">Цвет столба</label>
|
||||
<input id="pillar_color" type="text" :value="getColorNameFromRal(pillar_color)" class="w-60"
|
||||
disabled />
|
||||
<ColorPicker :cb="setPillarColor" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12">
|
||||
<div class="form-row">
|
||||
<div class="form-item">
|
||||
<label for="total_length">Общая длина забора, м</label>
|
||||
<input type="number" id="total_length" v-bind="parametric.total_length" min=0
|
||||
v-model="form_state.total_length" :ref="form_refs.total_length" />
|
||||
</div>
|
||||
<div class="form-item xl:w-2/4 text-sm xl:text-base">
|
||||
<p v-if="form_state.total_length_mm < parametric.length.min" class="text-ioprim">
|
||||
Выбранный размер забора слишком мал для расчета стоимости. Пожалуйста, выберите больший
|
||||
размер, чтобы продолжить.
|
||||
</p>
|
||||
<p v-if="form_state.extra_section" class="text-ioprim">
|
||||
Внимание! Дополнительная секция приводит к увеличению стоимости.
|
||||
Рекомендуем вам изменить длину забора или длину секции!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row min-h-12 mt-2 xl:mt-0">
|
||||
<div class="form-item form-item_checkbox">
|
||||
<input id="auto_length" type="checkbox" v-model="form_state.auto_length" />
|
||||
<label for="auto_length">Автоматический подбор секции</label>
|
||||
</div>
|
||||
<div class="form-item form-item_checkbox">
|
||||
<input id="remove_pillar" type="checkbox" v-model="form_state.remove_pillar" />
|
||||
<label for="remove_pillar">Без столбов</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="(form_state.total_length * 1000) >= parametric.length.min">
|
||||
<div class="col-span-12 xl:col-span-8 xl:col-start-3 grid calc_table">
|
||||
<div class="grid grid-cols-6">
|
||||
<div class="col-span-4 calc_table-maincell">Секции</div>
|
||||
<div class="col-span-2 calc_table-maincell">{{ section_count }}</div>
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
Ламели, RAL {{ lamelle_color }}, {{ getColorNameFromRal(lamelle_color)?.toLowerCase() }}
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-1">{{ section_count * lamelles_count }}</div>
|
||||
<div class="col-span-3 sm:col-span-1">
|
||||
{{ `${parseFloat(form_state.length.toString()).toFixed(2)}\xa0мм` }}</div>
|
||||
<template v-if="!form_state.remove_pillar">
|
||||
<div class="col-span-6 sm:col-span-4">Столбы, RAL {{ pillar_color }}, {{
|
||||
getColorNameFromRal(pillar_color)?.toLowerCase() }}</div>
|
||||
<div class="col-span-3 sm:col-span-1">
|
||||
{{ section_count + ~~(!!form_state.extra_section) + 1 }}
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-1">
|
||||
{{ `${parseFloat(form_state.fence_length.toString()).toFixed(2)}\xa0мм` }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="form_state.extra_section">
|
||||
<div class="col-span-4 calc_table-maincell">Дополнительная секция</div>
|
||||
<div class="col-span-2 calc_table-maincell">1</div>
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
Ламели, RAL {{ lamelle_color }}, {{ getColorNameFromRal(lamelle_color)?.toLowerCase() }}
|
||||
</div>
|
||||
<div class="col-span-3 sm:col-span-1">
|
||||
{{ 1 * lamelles_count }}</div>
|
||||
<div class="col-span-3 sm:col-span-1">{{
|
||||
`${parseFloat(form_state.extra_section.toString()).toFixed(2)}\xa0мм` }}</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 text-center mb-4">
|
||||
<button @click.prevent="toggleModal">Рассчитать</button>
|
||||
</div>
|
||||
</template>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
|
@ -1,50 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ralClassicPallette, getColorHexFromRal } from '@/components/ral'
|
||||
|
||||
const props = defineProps(['color', 'cb', 'open', 'active'])
|
||||
const open = props.open ?? true
|
||||
|
||||
const onClick = (color: string) => {
|
||||
if (props.cb) {
|
||||
props.cb(color)
|
||||
}
|
||||
toggleOpen()
|
||||
}
|
||||
const isOpenPicker = ref<boolean>(false)
|
||||
const toggleOpen = (value: boolean = !isOpenPicker) => {
|
||||
isOpenPicker.value = value !== undefined ? value : !isOpenPicker.value
|
||||
}
|
||||
const picker = ref()
|
||||
const clickOutside = (e: Event) => {
|
||||
if (!picker.value.contains(e.target)) {
|
||||
toggleOpen(false)
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', clickOutside)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', clickOutside)
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="color_picker" ref="picker">
|
||||
<div class="color_picker-selected" @click="open ? toggleOpen(!isOpenPicker) : onClick(props.color)"
|
||||
:style="[props.color && { backgroundColor: getColorHexFromRal(props.color) ?? '' }]"
|
||||
:class="[{ 'color_picker-selected__active': active }]"></div>
|
||||
<div class="color_picker-changer flex flex-wrap" v-if="isOpenPicker">
|
||||
<template v-for="col in ralClassicPallette">
|
||||
<div class="color size-5" :class="[{ 'outline outline-primary': props.color == col.hex }]"
|
||||
:style="[{ backgroundColor: col.hex }]" @click="onClick(col.code)">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.color_picker-selected:not([style*=color]) {
|
||||
background-image: conic-gradient(from 50deg, orange, yellow, green, cyan, blue, violet)
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { ralClassicPallette } from '@/components/ral'
|
||||
|
||||
const props = defineProps([ 'cb'])
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<template v-for="col in ralClassicPallette">
|
||||
<div class="picker-item"
|
||||
:style="[{ backgroundColor: col.hex }]" @click="props.cb(col.code)">
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
|
@ -0,0 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import { getFilename as getPattern, patterns } from '../pattern';
|
||||
import { getFilename as getTopper, toppers } from '../topper';
|
||||
|
||||
const props = defineProps(['cb', 'patterns']);
|
||||
type pType = {
|
||||
list: typeof patterns | typeof toppers,
|
||||
func: any
|
||||
}
|
||||
const p = {
|
||||
list: [],
|
||||
func: () => { }
|
||||
} as pType
|
||||
|
||||
if (props.patterns == 'pattern') {
|
||||
p.list = patterns;
|
||||
p.func = getPattern
|
||||
} else if (props.patterns == 'topper') {
|
||||
p.list = toppers
|
||||
p.func = getTopper
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<template v-for="item in p.list">
|
||||
<NuxtImg :src="p.func(item.id)" class="picker-item" @click="props.cb(item.id)" v-if="item.filename" />
|
||||
<span class="picker-item picker-item--empty" @click="props.cb(item.id)" v-else />
|
||||
</template>
|
||||
</template>
|
|
@ -0,0 +1,79 @@
|
|||
<script setup lang="ts">
|
||||
import { getFilename } from '../pattern';
|
||||
import { getColorHexFromRal, getColorNameFromRal } from '../ral';
|
||||
|
||||
const props = defineProps(['cb', 'name', 'color', 'pattern', 'type', 'disabled', 'goto_cam', 'goto_target'])
|
||||
|
||||
const goto_cam = use_goto_camera()
|
||||
const goto_target = use_goto_target()
|
||||
const open_calc = use_open_calc()
|
||||
|
||||
const picker = ref()
|
||||
const is_open = ref<boolean>(open_calc.value.includes(props.name))
|
||||
|
||||
const toggleOpen = (value: boolean = !is_open) => {
|
||||
is_open.value = value
|
||||
if (value == true) open_calc.value = [props.name]
|
||||
else if (value == false && open_calc.value.includes(props.name)) open_calc.value = []
|
||||
|
||||
if (value == true && props.goto_cam) goto_cam.value = props.goto_cam
|
||||
if (value == true && props.goto_target) goto_target.value = props.goto_target
|
||||
}
|
||||
|
||||
const onClick = (color: string) => {
|
||||
if (props.cb) {
|
||||
props.cb(color)
|
||||
}
|
||||
}
|
||||
|
||||
watch(open_calc, () => {
|
||||
if (open_calc.value.includes(props.name) && is_open.value !== true) {
|
||||
is_open.value = true
|
||||
} else if (!open_calc.value.includes(props.name) && is_open.value !== false) {
|
||||
is_open.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="picker" :class="[{
|
||||
'picker_open': is_open,
|
||||
'picker_disabled': props.disabled,
|
||||
}]" ref="picker">
|
||||
<div class="picker-input">
|
||||
<template v-if="props.type == 'color'">
|
||||
<div @click="toggleOpen(!is_open)"
|
||||
:style="[{ color: contrastColor(props.color), backgroundColor: getColorHexFromRal(props.color) ?? 'transparent' }]">
|
||||
<span>
|
||||
{{ getColorNameFromRal(props.color) }}
|
||||
</span>
|
||||
<Icon class="picker-button" :name="is_open ? 'mdi:chevron-up' : 'mdi:chevron-down'" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div @click="toggleOpen(!is_open)" :style="getFilename(props.pattern) ? [{
|
||||
backgroundImage: `url(${getFilename(props.pattern)})`,
|
||||
backgroundSize: 'contain',
|
||||
color: 'transparent'
|
||||
}] : []">
|
||||
<span>
|
||||
{{ props.pattern }}
|
||||
</span>
|
||||
<Icon class="picker-button" :name="is_open ? 'mdi:chevron-up' : 'mdi:chevron-down'" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="picker-changer" :class="[{ 'picker-changer_open': is_open }]">
|
||||
<template v-if="props.type == 'color'">
|
||||
<DropdownColor :cb="onClick" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<DropdownList :cb="onClick" :patterns="props.type" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.color_picker-selected:not([style*=color]) {
|
||||
background-image: conic-gradient(from 50deg, orange, yellow, green, cyan, blue, violet)
|
||||
}
|
||||
</style>
|
|
@ -18,23 +18,16 @@ const toggleExpState = () => {
|
|||
explosion_state.value = !explosion_state.value
|
||||
}
|
||||
const back_light = ref()
|
||||
const key_light = ref()
|
||||
const secondary_light = ref()
|
||||
const loadAll = async () => {
|
||||
const { scene: back } = await useGLTF('/models_light/back_exp.glb')
|
||||
const { scene: key } = await useGLTF('/models_light/key_exp.glb')
|
||||
const { scene: secondary } = await useGLTF('/models_light/secondary_exp.glb')
|
||||
|
||||
const k = 0.001
|
||||
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.01
|
||||
|
||||
key_light.value = key.children[0]
|
||||
key_light.value.intensity = key_light.value.intensity * k
|
||||
key_light.value.shadow.bias = -0.01
|
||||
key_light.value.cast_shadow = true
|
||||
back_light.value.shadow.bias = -0.02
|
||||
|
||||
secondary_light.value = secondary.children[0]
|
||||
secondary_light.value.intensity = secondary_light.value.intensity * k
|
||||
|
@ -63,17 +56,10 @@ onMounted(() => {
|
|||
<TresCanvas height="600">
|
||||
<TresPerspectiveCamera :position="[-7, 2, 4]" ref="camera" />
|
||||
<OrbitControls v-bind="controlsState" ref="controls" make-default />
|
||||
<ModelEnv />
|
||||
<Suspense>
|
||||
<ModelDiagram />
|
||||
</Suspense>
|
||||
|
||||
<TresPointLight v-bind="back_light" v-if="back_light"
|
||||
:position="[back_light.position.x, back_light.position.y, back_light.position.z]" />
|
||||
<TresPointLight v-bind="key_light" v-if="key_light"
|
||||
:position="[key_light.position.x, key_light.position.y, key_light.position.z]" />
|
||||
<TresPointLight v-bind="secondary_light" v-if="secondary_light"
|
||||
:position="[secondary_light.position.x, secondary_light.position.y, secondary_light.position.z]" />
|
||||
<!-- <TresAmbientLight :intensity="2" /> -->
|
||||
</TresCanvas>
|
||||
</ClientOnly>
|
||||
<div class="canvas-icons">
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
import { getColorNameFromRal } from '@/components/ral'
|
||||
import type { ralTypes } from '@/components/ral'
|
||||
import { apiFetch } from '~/utils/apiFetch';
|
||||
import { getName as getPatternName } from './pattern';
|
||||
import { getName as getTopperName } from './topper';
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const apiBase = config.public.apiBase
|
||||
|
@ -17,6 +19,8 @@ const section_count = useState('section_count')
|
|||
const extra_section = useState('extra_section')
|
||||
const total_length = useState('total_length')
|
||||
const remove_pillar = useState<boolean>('remove_pillar')
|
||||
const pillar_topper = use_topper()
|
||||
const pillar_pattern = use_pattern()
|
||||
|
||||
const toggleModal = () => {
|
||||
modal_data.email = undefined
|
||||
|
@ -143,6 +147,8 @@ const total_txt = computed(() => {
|
|||
lam_quad_regular: discountValue * lamelles_block * lam_count * lamelle_height.value * length_m,
|
||||
lam_quad_extra: discountValue * lamelles_block * lam_count * lamelle_height.value * extra_m,
|
||||
}
|
||||
const isPattern = pillar_pattern.value !== 0;
|
||||
const isTopper = pillar_topper.value !== 0;
|
||||
// console.log(prices)
|
||||
const extra = extra_section.value ? {
|
||||
pillar: !remove_pillar.value && {
|
||||
|
@ -164,6 +170,24 @@ const total_txt = computed(() => {
|
|||
value: prices.lam_quad_regular * sections
|
||||
},
|
||||
};
|
||||
const decor = {} as { [key: string]: { txt: string, value?: string } }
|
||||
if (
|
||||
!remove_pillar.value
|
||||
&& (
|
||||
pillar_pattern.value !== 0
|
||||
|| pillar_topper.value !== 0
|
||||
)
|
||||
) {
|
||||
const els = [];
|
||||
if (isPattern) els.push('нанесения узоров на опорные столбы')
|
||||
if (isTopper) els.push('индивидуального дизайна колпаков')
|
||||
const txt = `Стоимость ${els.join(' и ')} рассчитывается отдельно.`
|
||||
decor.el = {
|
||||
txt: txt,
|
||||
}
|
||||
if (isPattern) decor.pattern = { txt: 'Узор опорного столба', value: getPatternName(pillar_pattern.value) }
|
||||
if (isTopper) decor.topper = { txt: 'Колпак', value: getTopperName(pillar_topper.value) }
|
||||
}
|
||||
|
||||
const total = [extra, regular].map(item => Object.values(item).map(el => el ? el.value : 0)).flat().reduce((a, b) => a + b, 0)
|
||||
|
||||
|
@ -173,11 +197,18 @@ const total_txt = computed(() => {
|
|||
const res_extra = extra_section.value ? Object.values(extra).map(item =>
|
||||
Object.entries(item).map(el => el[0] == 'value' ? roubleSign.format(el[1] as number) : el[1]).join(': ')
|
||||
).filter(Boolean) : []
|
||||
const res_decor = Object.values(decor).map(item =>
|
||||
Object.entries(item).map(el => el[1]).join(': ')
|
||||
).filter(Boolean)
|
||||
|
||||
const res_total = [`Итого ${roubleSign.format(total)}`]
|
||||
const total_str = []
|
||||
if (isPattern) total_str.push('узора')
|
||||
if (isTopper) total_str.push('колпаков')
|
||||
const res_total = [`Итого${total_str.length ? ' без ' + total_str.join(' и ') : ''}: ${roubleSign.format(total)}`]
|
||||
return {
|
||||
regular: res_regular,
|
||||
extra: res_extra,
|
||||
decor: res_decor,
|
||||
total: res_total
|
||||
}
|
||||
})
|
||||
|
@ -243,8 +274,8 @@ const policy = () => {
|
|||
Цвет столба: {{ getColorNameFromRal(pillar_color) }}<br />
|
||||
Цвет ламелей: {{ getColorNameFromRal(lamelle_color) }}
|
||||
</p>
|
||||
<template v-for="item in total_txt">
|
||||
<p v-if="item.length">
|
||||
<template v-for="(item, i) in total_txt">
|
||||
<p v-if="item.length" :class="[{ 'text-ioprim': i == 'decor' }]">
|
||||
<template v-for="i in item">{{ i }}<br /></template>
|
||||
</p>
|
||||
</template>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
//@ts-ignore
|
||||
import { useGLTF } from '@tresjs/cientos'
|
||||
import { getColorHexFromRal } from '../ral';
|
||||
|
||||
|
@ -14,12 +15,12 @@ const targetExplosion = {
|
|||
verh: [0 * k, 0.25 * k, 0 * k],
|
||||
}
|
||||
|
||||
const { scene: kosynka } = await useGLTF('/models/kosynka.glb')
|
||||
const { scene: krepleniye_planok } = await useGLTF('/models/krepleniye_planok.glb')
|
||||
const { scene: osnova_stolba } = await useGLTF('/models/osnova_stolba.glb')
|
||||
const { scene: planki } = await useGLTF('/models/planki.glb')
|
||||
const { scene: stolb } = await useGLTF('/models/stolb.glb')
|
||||
const { scene: verh } = await useGLTF('/models/verh.glb')
|
||||
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()
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
PCFSoftShadowMap,
|
||||
CineonToneMapping,
|
||||
PMREMGenerator,
|
||||
Vector3,
|
||||
} from 'three';
|
||||
import { GainMapLoader, } from '@monogrid/gainmap-js'
|
||||
import { useLoop } from '@tresjs/core';
|
||||
const { scene, renderer, camera } = useTresContext()
|
||||
const {onBeforeRender} = useLoop()
|
||||
|
||||
renderer.value.toneMapping = CineonToneMapping
|
||||
renderer.value.toneMappingExposure = 1
|
||||
|
||||
renderer.value.shadowMap.enabled = true
|
||||
renderer.value.shadowMap.type = PCFSoftShadowMap
|
||||
|
||||
const pmremGenerator = new PMREMGenerator(renderer.value);
|
||||
pmremGenerator.compileEquirectangularShader();
|
||||
onMounted(async () => {
|
||||
const loader = new GainMapLoader(renderer.value)
|
||||
const result = await loader.loadAsync([
|
||||
'hdrmaps/hdr.webp',
|
||||
'hdrmaps/hdr-gainmap.webp',
|
||||
'hdrmaps/hdr.json',
|
||||
])
|
||||
if (renderer.value && camera.value) {
|
||||
renderer.value.render(scene.value, camera.value)
|
||||
}
|
||||
const exrCubeRenderTarget = pmremGenerator.fromEquirectangular(result.renderTarget.texture);
|
||||
const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null;
|
||||
|
||||
scene.value.environment = newEnvMap
|
||||
scene.value.environmentIntensity = 1
|
||||
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
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template></template>
|
|
@ -1,7 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { BufferGeometry, Matrix4, Vector3 } from 'three';
|
||||
import { BufferGeometry, Matrix4, Mesh, Object3D, Vector3 } from 'three';
|
||||
import { getColorHexFromRal } from '../ral';
|
||||
|
||||
const props = defineProps(['index', 'models'])
|
||||
const props = defineProps(['index', 'models', 'last_element', 'first_element'])
|
||||
|
||||
const lamelle_height = use_lamelle_height()
|
||||
const lamelles_count = use_lamelles_count()
|
||||
|
@ -9,33 +10,39 @@ const fence_section = use_fence_section()
|
|||
const section_count = use_section_count()
|
||||
const extra_section = use_extra_section()
|
||||
const remove_pillar = use_remove_pillar()
|
||||
const max_size = use_max_size()
|
||||
|
||||
const pillar_color = use_pillar_color()
|
||||
const pillar_pattern = use_pattern()
|
||||
const pillar_topper = use_topper()
|
||||
const lamelle_color = use_lamelle_color()
|
||||
|
||||
const lSize = lamelle_height.value
|
||||
const bSize = 0.0235
|
||||
const pillar_size = 104 * 0.001
|
||||
|
||||
const pillar_one_pos = ref(fence_section.value * -0.5 - 0.015)
|
||||
const pillar_two_pos = ref(fence_section.value * 0.5 + pillar_size + bSize - 0.01)
|
||||
const pillar_one_pos = ref()
|
||||
const pillar_two_pos = ref()
|
||||
|
||||
const scale_koef = 2.5
|
||||
const show_pillar_one = ref(props.index == 1)
|
||||
const scale_koef = 1
|
||||
const show_pillar_one = ref(props.first_element)
|
||||
const show_pillar_two = ref(true)
|
||||
|
||||
const last_element = ref(Math.min((section_count.value + ~~(!!extra_section.value)), max_size.value))
|
||||
watch([section_count, fence_section, extra_section], () => {
|
||||
last_element.value = Math.min(section_count.value + ~~(!!extra_section.value), max_size.value)
|
||||
})
|
||||
|
||||
const getExtraValue = () => (extra_section.value && props.index == last_element.value) ? extra_section.value * 0.001 : false
|
||||
const extra = ref(getExtraValue())
|
||||
if (extra.value) {
|
||||
pillar_one_pos.value = (extra.value as number) * -0.5 - 0.015
|
||||
pillar_two_pos.value = (extra.value as number) * 0.5 + pillar_size + bSize - 0.01
|
||||
const setPillarValues = () => {
|
||||
extra.value = getExtraValue()
|
||||
if (extra_section.value && props.last_element && !props.first_element) {
|
||||
pillar_one_pos.value = (extra.value as number) * -0.5
|
||||
pillar_two_pos.value = (extra.value as number) * 0.5 + pillar_size
|
||||
} else {
|
||||
pillar_one_pos.value = fence_section.value * -0.5
|
||||
pillar_two_pos.value = fence_section.value * 0.5 + pillar_size
|
||||
}
|
||||
}
|
||||
|
||||
const getExtraValue = () => (extra_section.value && props.last_element && !props.first_element) ? extra_section.value * 0.001 : false
|
||||
const extra = ref(getExtraValue())
|
||||
setPillarValues()
|
||||
|
||||
const make_translate_to_section = (source = fence_section.value) => {
|
||||
const one_s = (source + pillar_size + bSize) * scale_koef
|
||||
const one_s = (source + (remove_pillar.value ? 0 : pillar_size)) * scale_koef
|
||||
let r = (props.index - 1) * one_s
|
||||
if (typeof extra.value == 'number') {
|
||||
r -= (fence_section.value - extra.value) * scale_koef * 0.5
|
||||
|
@ -47,84 +54,222 @@ watch([fence_section, extra], () => {
|
|||
translate_to_section.value = make_translate_to_section()
|
||||
})
|
||||
|
||||
set_material({ children: [props.models.pillar_brace, props.models.fixing] }, '#111');
|
||||
|
||||
const instanced_lamelle = shallowRef();
|
||||
const instanced_lamelle_geometry = Object.assign(new BufferGeometry, props.models.lamelle.children[0].geometry)
|
||||
const instanced_lamelle_material = props.models.lamelle.children[0].material
|
||||
const instanced_lamelle_count = 24
|
||||
const instanced_v = [instanced_lamelle_geometry, instanced_lamelle_material, instanced_lamelle_count]
|
||||
watch([instanced_lamelle, fence_section, extra_section, lamelles_count, extra], () => {
|
||||
// console.log(props.index, instanced_v, instanced_lamelle.value)
|
||||
const instanced_lamelle_el = [
|
||||
Object.assign(new BufferGeometry, props.models.lamelle.children[0].geometry),
|
||||
props.models.lamelle.children[0].material,
|
||||
instanced_lamelle_count
|
||||
]
|
||||
|
||||
const instanced_fixing_one = shallowRef();
|
||||
const instanced_fixing_one_el = [
|
||||
Object.assign(new BufferGeometry, props.models.fixing.children[0].geometry),
|
||||
props.models.fixing.children[0].material,
|
||||
instanced_lamelle_count
|
||||
]
|
||||
|
||||
const instanced_fixing_two = shallowRef();
|
||||
const instanced_fixing_two_el = [
|
||||
Object.assign(new BufferGeometry, props.models.fixing.children[0].geometry),
|
||||
props.models.fixing.children[0].material,
|
||||
instanced_lamelle_count
|
||||
]
|
||||
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_z = 0.022 * scale_koef
|
||||
return new Matrix4().fromArray([
|
||||
scale_x, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
pos_x, pos_y, pos_z, 1
|
||||
])
|
||||
}
|
||||
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_z = 0.022 * scale_koef
|
||||
return new Matrix4().fromArray([
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
pos_x, pos_y, pos_z, 1
|
||||
])
|
||||
}
|
||||
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_z = 0.022 * scale_koef
|
||||
return new Matrix4().fromArray([
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
pos_x, pos_y, pos_z, 1
|
||||
])
|
||||
}
|
||||
|
||||
watch([section_count, fence_section, extra_section], setPillarValues)
|
||||
|
||||
const brace = props.models.pillar_brace.clone().children[0]
|
||||
const braces = ref<Mesh[]>([])
|
||||
const setBraceCount = () => {
|
||||
const brace_count = Math.floor(lamelles_count.value / 4)
|
||||
const arr: Mesh[] = []
|
||||
for (let index = 0; index < brace_count; index++) {
|
||||
const brace_item = brace.clone()
|
||||
brace_item.position.setComponent(1, lamelle_height.value * 4 * (index + 0.75))
|
||||
arr.push(brace_item)
|
||||
}
|
||||
braces.value = arr
|
||||
}
|
||||
|
||||
setBraceCount()
|
||||
watch(lamelles_count, setBraceCount)
|
||||
|
||||
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);
|
||||
|
||||
const pillar_outer = props.models.pillar_center.children[0];
|
||||
pillar_outer.scale.setComponent(1, lamelles_count.value);
|
||||
|
||||
const pillar_inner = props.models.pillar_inner.children[0];
|
||||
pillar_inner.scale.setComponent(1, lamelles_count.value);
|
||||
|
||||
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 => {
|
||||
set_material({ children: [el] }, getColorHexFromRal(pillar_color.value), undefined, true)
|
||||
})
|
||||
set_material(
|
||||
{ children: [arr[2]] },
|
||||
getColorHexFromRal(pillar_color.value),
|
||||
{ pattern: pillar_pattern.value, count: lamelles_count.value },
|
||||
true
|
||||
)
|
||||
pillar.value = arr.map(el => el.clone())
|
||||
}
|
||||
setPillar()
|
||||
watch([pillar_pattern, pillar_color, pillar_topper, fence_section, lamelles_count], 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,
|
||||
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,
|
||||
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,
|
||||
0
|
||||
);
|
||||
top.scale.setComponent(0, v);
|
||||
|
||||
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)
|
||||
|
||||
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);
|
||||
|
||||
let arr = [top_one, top_two, top, side_one, side_two];
|
||||
[top, side_one, side_two, ...braces.value].map(el => {
|
||||
set_material({ children: [el] }, getColorHexFromRal(pillar_color.value))
|
||||
})
|
||||
fastening.value = arr.map(el => el.clone())
|
||||
}
|
||||
setFastening()
|
||||
watch([pillar_color, lamelles_count, pillar_one_pos, pillar_two_pos], setFastening)
|
||||
|
||||
const setLamellesColor = () => {
|
||||
if (instanced_lamelle.value) {
|
||||
set_material({ children: [instanced_lamelle.value] }, getColorHexFromRal(lamelle_color.value))
|
||||
}
|
||||
}
|
||||
setLamellesColor()
|
||||
watch([instanced_lamelle, lamelle_color], setLamellesColor)
|
||||
|
||||
watch([
|
||||
instanced_lamelle,
|
||||
lamelles_count,
|
||||
fence_section,
|
||||
], () => {
|
||||
const translationVector = new Vector3(0, 20, 20)
|
||||
for (let i = 0; i < instanced_lamelle_count; i++) {
|
||||
if (instanced_lamelle.value) {
|
||||
const scale_x = (((extra.value as number) || fence_section.value) * 10)
|
||||
const pos_x = pillar_size * 0.5
|
||||
const pos_y = (lSize * i)
|
||||
const pos_z = 0.02
|
||||
const newmatrix = new Matrix4().fromArray([
|
||||
scale_x, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
pos_x, pos_y, pos_z, 1
|
||||
])
|
||||
instanced_lamelle.value.setMatrixAt(i, newmatrix);
|
||||
|
||||
instanced_lamelle.value.setMatrixAt(i, lamelleMatrix(i));
|
||||
if (i >= lamelles_count.value) {
|
||||
instanced_lamelle.value.setMatrixAt(i, new Matrix4().makeTranslation(new Vector3(0, 20, 20)));
|
||||
instanced_lamelle.value.setMatrixAt(i, new Matrix4().makeTranslation(translationVector));
|
||||
}
|
||||
instanced_lamelle.value.instanceMatrix.needsUpdate = true
|
||||
}
|
||||
if (instanced_fixing_one.value) {
|
||||
instanced_fixing_one.value.setMatrixAt(i, fixingOneMatrix(i));
|
||||
if (i >= lamelles_count.value) {
|
||||
instanced_fixing_one.value.setMatrixAt(i, new Matrix4().makeTranslation(translationVector));
|
||||
}
|
||||
instanced_fixing_one.value.instanceMatrix.needsUpdate = true
|
||||
}
|
||||
if (instanced_fixing_two.value) {
|
||||
instanced_fixing_two.value.setMatrixAt(i, fixingTwoMatrix(i));
|
||||
if (i >= lamelles_count.value) {
|
||||
instanced_fixing_two.value.setMatrixAt(i, new Matrix4().makeTranslation(translationVector));
|
||||
}
|
||||
instanced_fixing_two.value.instanceMatrix.needsUpdate = true
|
||||
}
|
||||
instanced_lamelle.value.instanceMatrix.needsUpdate = true
|
||||
}
|
||||
});
|
||||
|
||||
watch([section_count, fence_section, extra_section], () => {
|
||||
extra.value = getExtraValue()
|
||||
if (extra_section.value && props.index == last_element.value) {
|
||||
pillar_one_pos.value = (extra.value as number) * -0.5 - 0.015
|
||||
pillar_two_pos.value = (extra.value as number) * 0.5 + pillar_size + bSize - 0.01
|
||||
// translate_to_section.value = make_translate_to_section(extra_section.value * 0.001)
|
||||
} else {
|
||||
pillar_one_pos.value = fence_section.value * -0.5 - 0.015
|
||||
pillar_two_pos.value = fence_section.value * 0.5 + pillar_size + bSize - 0.01
|
||||
// translate_to_section.value = make_translate_to_section()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<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"
|
||||
:position-z="0">
|
||||
<TresGroup :position-y="(lSize * -0.5)" :scale="[1, 0.5, 1]">
|
||||
<TresObject3D v-bind="props.models.fence.children[0]" />
|
||||
</TresGroup>
|
||||
<TresObject3D v-bind="props.models.fence.children[0]" :scale-y="lamelles_count" />
|
||||
<TresGroup :position-y="(lSize * lamelles_count)" :scale="[1, 0.5, 1]">
|
||||
<TresObject3D v-bind="props.models.fence.children[0]" />
|
||||
</TresGroup>
|
||||
<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()" />
|
||||
</template>
|
||||
<template v-for="item in braces">
|
||||
<TresMesh v-bind="item.clone()" :name="`brace_one`" />
|
||||
</template>
|
||||
</TresGroup>
|
||||
|
||||
<TresGroup name="pillar_two" v-if="!remove_pillar && show_pillar_two" :position-x="pillar_two_pos"
|
||||
:position-z="0">
|
||||
<TresGroup :position-y="(lSize * -0.5)" :scale="[-1, 0.5, 1]">
|
||||
<TresObject3D v-bind="props.models.fence.children[0]" />
|
||||
</TresGroup>
|
||||
<TresObject3D v-bind="props.models.fence.children[0]" :scale="[-1, lamelles_count, 1]" />
|
||||
<TresGroup :position-y="(lSize * lamelles_count)" :scale="[-1, 0.5, 1]">
|
||||
<TresObject3D v-bind="props.models.fence.children[0]" />
|
||||
</TresGroup>
|
||||
<TresGroup name="pillar_two" v-if="!remove_pillar && show_pillar_two" :position-x="pillar_two_pos">
|
||||
<template v-for="item in pillar">
|
||||
<TresObject3D v-bind="item.clone()" />
|
||||
</template>
|
||||
<template v-for="item in braces">
|
||||
<TresMesh v-bind="item.clone()" :name="`brace_two`" />
|
||||
</template>
|
||||
</TresGroup>
|
||||
|
||||
<TresGroup name="lamelles">
|
||||
<TresInstancedMesh ref="instanced_lamelle" :args="instanced_v" cast-shadow />
|
||||
</TresGroup>
|
||||
<TresGroup name="lam_fastening_one" :position-x="pillar_one_pos" :scale-y="lamelles_count">
|
||||
<TresObject3D v-bind="props.models.fastening.children[0]" />
|
||||
</TresGroup>
|
||||
<TresGroup name="lam_fastening_two" :position-x="pillar_two_pos" :scale="[-1, lamelles_count, 1]">
|
||||
<TresObject3D v-bind="props.models.fastening.children[0]" />
|
||||
</TresGroup>
|
||||
<TresGroup name="top_section" :scale-x="((extra as number) || fence_section) * 10 + 0.1 * scale_koef"
|
||||
:position="[pillar_size * 0.5, lamelles_count * lSize, 0]">
|
||||
<TresObject3D v-bind="props.models.top.children[0]" />
|
||||
<TresInstancedMesh ref="instanced_lamelle" :args="instanced_lamelle_el" />
|
||||
<TresInstancedMesh ref="instanced_fixing_one" :args="instanced_fixing_one_el" />
|
||||
<TresInstancedMesh ref="instanced_fixing_two" :args="instanced_fixing_two_el" />
|
||||
</TresGroup>
|
||||
|
||||
<template v-for="item in fastening">
|
||||
<TresObject3D v-bind="item.clone()" />
|
||||
</template>
|
||||
</TresGroup>
|
||||
</template>
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useGLTF, Text3D } from '@tresjs/cientos'
|
||||
import { DoubleSide, Object3D, Vector3 } from 'three';
|
||||
// @ts-ignore
|
||||
import { useGLTF } from '@tresjs/cientos'
|
||||
import { Vector3 } from 'three';
|
||||
const props = defineProps(['modelUrl', 'model', 'position', 'target', 'parent'])
|
||||
|
||||
let scene: any
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<script setup lang="ts">
|
||||
import { useLoop, useTresContext } from '@tresjs/core';
|
||||
import { Box3, Object3D, Vector3 } from 'three';
|
||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||
|
||||
const props = defineProps(['number', 'count', 'models'])
|
||||
const rotate = () => {
|
||||
switch (props.number) {
|
||||
case 1:
|
||||
return degToRad(0)
|
||||
case 2:
|
||||
return degToRad(180)
|
||||
case 3:
|
||||
return degToRad(270)
|
||||
case 4:
|
||||
return degToRad(90)
|
||||
}
|
||||
}
|
||||
const { seekByName } = useSeek()
|
||||
const { scene, camera } = useTresContext()
|
||||
|
||||
const section_count = use_section_count()
|
||||
const extra_section = use_extra_section()
|
||||
const fence_section = use_fence_section()
|
||||
const max_size = use_max_size()
|
||||
const lamelle_count = use_lamelles_count()
|
||||
const lamelle_height = use_lamelle_height()
|
||||
|
||||
const total = ref((section_count.value + ~~(!!extra_section.value)))
|
||||
const position = ref(new Vector3())
|
||||
|
||||
const count_pos = () => {
|
||||
total.value = (section_count.value + ~~(!!extra_section.value))
|
||||
|
||||
const line = seekByName(scene.value, `line_${props.number}`)
|
||||
const line_size = new Vector3()
|
||||
const line_pos = new Vector3()
|
||||
|
||||
if (line && line.children.length) {
|
||||
line.updateMatrixWorld()
|
||||
new Box3().expandByObject(line).getSize(line_size)
|
||||
line.getWorldPosition(line_pos)
|
||||
}
|
||||
const line1 = seekByName(scene.value, `line_1_inner`);
|
||||
const line1_size = new Vector3()
|
||||
const line1_pos = new Vector3()
|
||||
if (line1) {
|
||||
new Box3().expandByObject(line1).getSize(line1_size)
|
||||
line1.getWorldPosition(line1_pos)
|
||||
}
|
||||
const k = (line1_size.x / props.count) * 0.5 - 0.065
|
||||
switch (props.number) {
|
||||
case 1: break;
|
||||
case 2:
|
||||
position.value.z = line1_size.x * -1
|
||||
position.value.x = line1_size.x - k * 2
|
||||
break;
|
||||
case 3:
|
||||
position.value.z = line1_size.x * -1 + k
|
||||
position.value.x = -1 * k
|
||||
break;
|
||||
case 4:
|
||||
position.value.z = -1 * k
|
||||
position.value.x = line1_size.x - k
|
||||
break;
|
||||
}
|
||||
line?.updateMatrixWorld()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
count_pos()
|
||||
})
|
||||
watch(() => [props.count, fence_section.value, section_count.value, extra_section.value, lamelle_count.value],
|
||||
count_pos,
|
||||
{ flush: 'post' }
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<TresGroup :name="`line_${props.number}`" :rotate-y="rotate()" :position-x="position.x" :position-y="position.y"
|
||||
:position-z="position.z">
|
||||
<TresGroup :name="`line_${props.number}_inner`">
|
||||
<template v-for="i in props.count">
|
||||
<template v-if="(i + (props.number - 1) * props.count) <= Math.min(total, max_size)"
|
||||
:key="(i + (props.number - 1) * props.count)">
|
||||
<ModelFence :index="i" :models="props.models"
|
||||
:last_element="(i + (props.number - 1) * props.count) == total"
|
||||
:first_element="i == 1 && props.number == 1" />
|
||||
</template>
|
||||
</template>
|
||||
</TresGroup>
|
||||
</TresGroup>
|
||||
</template>
|
|
@ -1,92 +1,129 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
PCFSoftShadowMap,
|
||||
EquirectangularReflectionMapping,
|
||||
CineonToneMapping,
|
||||
Euler,
|
||||
} from 'three';
|
||||
import { GainMapLoader, } from '@monogrid/gainmap-js'
|
||||
import { Object3D, Vector3 } from 'three';
|
||||
//@ts-ignore
|
||||
import { useGLTF, } from '@tresjs/cientos'
|
||||
import { getColorHexFromRal, type ralTypes } from '../ral';
|
||||
import type { OrbitControlsProps } from '@tresjs/cientos/dist/core/controls/OrbitControls.vue.js';
|
||||
import { getModel, toppers, type toppersIds } from '../topper';
|
||||
|
||||
const pillar_color = use_pillar_color()
|
||||
const lamelle_color = use_lamelle_color()
|
||||
const section_count = use_section_count()
|
||||
const extra_section = use_extra_section()
|
||||
const max_size = use_max_size()
|
||||
const lamelles_count = use_lamelles_count()
|
||||
const lamelle_height = use_lamelle_height()
|
||||
const fence_section = use_fence_section()
|
||||
|
||||
const { scene, renderer, camera } = useTresContext()
|
||||
renderer.value.toneMapping = CineonToneMapping
|
||||
renderer.value.toneMappingExposure = 0.5
|
||||
// renderer.value.gammaOutput = true
|
||||
const pillar_topper = use_topper()
|
||||
|
||||
renderer.value.shadowMap.enabled = true
|
||||
renderer.value.shadowMap.type = PCFSoftShadowMap
|
||||
const open_calc = use_open_calc()
|
||||
const goto_cam = use_goto_camera()
|
||||
const goto_target = use_goto_target()
|
||||
|
||||
const { scene: top_model } = await useGLTF('/models_one/verh_100.glb', { draco: true })
|
||||
const { scene: fence_model } = await useGLTF('/models_one/fence.glb', { draco: true })
|
||||
const { scene: fastening_model } = await useGLTF('/models_one/krepleniye_planok (1).glb', { draco: true })
|
||||
const { scene: lamelle_model } = await useGLTF('/models_one/lamel_100.glb', { draco: true })
|
||||
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 { 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 top = ref(top_model)
|
||||
const fence = ref(fence_model)
|
||||
const fastening = ref(fastening_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)
|
||||
|
||||
set_material(lamelle.value, getColorHexFromRal(lamelle_color.value));
|
||||
[top, fence, fastening].map((el: any) => { set_material(el.value, getColorHexFromRal(pillar_color.value)) })
|
||||
watch(pillar_topper, async () => {
|
||||
pillar_top.value = topper_models[pillar_topper.value]
|
||||
})
|
||||
|
||||
const { seek, seekAll } = useSeek()
|
||||
watch(lamelle_color, () => {
|
||||
set_material(lamelle.value, getColorHexFromRal(lamelle_color.value))
|
||||
const items = seekAll(scene.value, 'name', "lamelles")
|
||||
items.forEach(element => {
|
||||
set_material(element, getColorHexFromRal(lamelle_color.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)]
|
||||
}
|
||||
});
|
||||
})
|
||||
watch(pillar_color, () => {
|
||||
[top, fence, fastening].map((el: any) => { set_material(el.value, getColorHexFromRal(pillar_color.value)) })
|
||||
const items = [
|
||||
...seekAll(scene.value, 'name', "pillar_one"),
|
||||
...seekAll(scene.value, 'name', "pillar_two"),
|
||||
...seekAll(scene.value, 'name', "lam_fastening_one"),
|
||||
...seekAll(scene.value, 'name', "lam_fastening_two"),
|
||||
...seekAll(scene.value, 'name', "top_section"),
|
||||
]
|
||||
items.forEach(element => {
|
||||
set_material(element, getColorHexFromRal(pillar_color.value))
|
||||
})
|
||||
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([section_count, extra_section], () => {
|
||||
const base = seek(scene.value, 'name', 'base')
|
||||
const n = (section_count.value as number) + ~~(!!extra_section.value)
|
||||
if (base?.children && n < base?.children.length) {
|
||||
base.children = [...base?.children.slice(0, n)]
|
||||
|
||||
watch(open_calc, () => {
|
||||
if (Object.keys(open_calc.value).length == 0) {
|
||||
setTarget(true)
|
||||
}
|
||||
})
|
||||
const min_for_square = 12;
|
||||
|
||||
onMounted(async () => {
|
||||
const loader = new GainMapLoader(renderer.value)
|
||||
const result = await loader.loadAsync([
|
||||
'hdrmaps/hdr.webp',
|
||||
'hdrmaps/hdr-gainmap.webp',
|
||||
'hdrmaps/hdr.json',
|
||||
])
|
||||
renderer.value.render(scene.value, camera.value)
|
||||
scene.value.environment = result.renderTarget.texture
|
||||
scene.value.background = result.renderTarget.texture
|
||||
scene.value.background.mapping = EquirectangularReflectionMapping
|
||||
scene.value.backgroundRotation = new Euler(0, 10, 0)
|
||||
scene.value.backgroundIntensity = 1.5
|
||||
scene.value.backgroundBlurriness = 0.06
|
||||
result.renderTarget.texture.dispose();
|
||||
})
|
||||
|
||||
setTarget()
|
||||
</script>
|
||||
<template>
|
||||
<TresGroup name="base">
|
||||
<template v-for="i in Math.min((section_count + ~~(!!extra_section)), max_size)">
|
||||
<ModelFence :index="i" :models="{ top, fence, fastening, lamelle }" />
|
||||
<template v-for="line in (total >= min_for_square) ? 4 : 1" :key="`${line}_${count}`">
|
||||
<ModelLine :models="{
|
||||
top,
|
||||
pillar_center, pillar_top, pillar_bottom, pillar_inner,
|
||||
pillar_brace,
|
||||
fastening_top, fastening_side, fixing,
|
||||
lamelle
|
||||
}" :number="line" :count="count" />
|
||||
</template>
|
||||
</TresGroup>
|
||||
</template>
|
|
@ -0,0 +1,56 @@
|
|||
<script setup lang="ts">
|
||||
import { Vector3 } from 'three';
|
||||
|
||||
const { controls, camera } = useTresContext();
|
||||
const goto_camera = use_goto_camera();
|
||||
const goto_target = use_goto_target();
|
||||
|
||||
const COUNT = 60
|
||||
type smooth = {
|
||||
value: Vector3 | undefined,
|
||||
count: number
|
||||
}
|
||||
const smooth_target = reactive({}) as smooth
|
||||
const smooth_move = reactive({}) as smooth
|
||||
|
||||
const set_moveto = (obj: smooth, value: smooth["value"]) => {
|
||||
obj.value = value
|
||||
obj.count = COUNT
|
||||
}
|
||||
|
||||
watch(goto_target, () => {
|
||||
smooth_target.value = goto_target.value
|
||||
smooth_target.count = COUNT
|
||||
})
|
||||
watch(goto_camera, () => {
|
||||
smooth_move.value = goto_camera.value
|
||||
smooth_move.count = COUNT
|
||||
})
|
||||
|
||||
const koef = (1 / COUNT) * 3
|
||||
const { onBeforeLoop } = useRenderLoop()
|
||||
onBeforeLoop(() => {
|
||||
if (smooth_target.value || smooth_move.value) {
|
||||
if (smooth_target.value) {
|
||||
(controls.value as any).target.lerp(smooth_target.value as Vector3, koef);
|
||||
(controls.value as any).update();
|
||||
smooth_target.count -= 1;
|
||||
if (smooth_target.count == 1) {
|
||||
set_moveto(smooth_target, undefined);
|
||||
}
|
||||
}
|
||||
if (smooth_move.value) {
|
||||
camera.value?.position.lerp(smooth_move.value as Vector3, koef);
|
||||
camera.value?.updateMatrixWorld();
|
||||
(controls.value as any).update();
|
||||
|
||||
smooth_move.count -= 1;
|
||||
if (smooth_move.count == 1) {
|
||||
set_moveto(smooth_move, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
<template></template>
|
|
@ -0,0 +1,19 @@
|
|||
export const patterns = [
|
||||
{ name: 'Без узора', },
|
||||
{ name: 'Узор 1', filename: 'tile1.png' },
|
||||
{ name: 'Узор 2', filename: 'tile2.jpg' },
|
||||
].map((el, i) => Object.assign(el, { id: i }))
|
||||
|
||||
export const getName = (id: patternIds) => {
|
||||
const el = patterns.find(el => el.id == id)
|
||||
if (!el) return undefined
|
||||
return el.name
|
||||
}
|
||||
|
||||
export const getFilename = (id: patternIds) => {
|
||||
const el = patterns.find(el => el.id == id)
|
||||
if (!el || !el.filename) return undefined
|
||||
return `/pattern/${el?.filename}`
|
||||
}
|
||||
|
||||
export type patternIds = typeof patterns[number]['id']
|
|
@ -1,7 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps(['forms', 'n'])
|
||||
const ruOrdinalRules = new Intl.PluralRules("ru-RU");
|
||||
</script>
|
||||
<template>
|
||||
{{ props.forms[ruOrdinalRules.select(props.n)] }}
|
||||
</template>
|
|
@ -137,12 +137,6 @@ export const ralClassicPallette = {
|
|||
"hex": "#B89C50",
|
||||
"groupId": "ral_classic_1"
|
||||
},
|
||||
"1026": {
|
||||
"code": "1026",
|
||||
"name": "Люминесцентный жёлтый",
|
||||
"hex": "#F5FF00",
|
||||
"groupId": "ral_classic_1"
|
||||
},
|
||||
"1027": {
|
||||
"code": "1027",
|
||||
"name": "Карри жёлтый",
|
||||
|
@ -173,18 +167,6 @@ export const ralClassicPallette = {
|
|||
"hex": "#EDAB56",
|
||||
"groupId": "ral_classic_1"
|
||||
},
|
||||
"1035": {
|
||||
"code": "1035",
|
||||
"name": "Перламутрово-бежевый",
|
||||
"hex": "#A29985",
|
||||
"groupId": "ral_classic_1"
|
||||
},
|
||||
"1036": {
|
||||
"code": "1036",
|
||||
"name": "Перламутрово-золотой",
|
||||
"hex": "#927549",
|
||||
"groupId": "ral_classic_1"
|
||||
},
|
||||
"1037": {
|
||||
"code": "1037",
|
||||
"name": "Солнечно-жёлтый",
|
||||
|
@ -221,18 +203,7 @@ export const ralClassicPallette = {
|
|||
"hex": "#E75B12",
|
||||
"groupId": "ral_classic_2"
|
||||
},
|
||||
"2005": {
|
||||
"code": "2005",
|
||||
"name": "Люминесцентный оранжевый",
|
||||
"hex": "#FF2300",
|
||||
"groupId": "ral_classic_2"
|
||||
},
|
||||
"2007": {
|
||||
"code": "2007",
|
||||
"name": "Люминесцентный ярко-оранжевый",
|
||||
"hex": "#FFA421",
|
||||
"groupId": "ral_classic_2"
|
||||
},
|
||||
|
||||
"2008": {
|
||||
"code": "2008",
|
||||
"name": "Ярко-красно-оранжевый",
|
||||
|
@ -263,12 +234,6 @@ export const ralClassicPallette = {
|
|||
"hex": "#DB6A50",
|
||||
"groupId": "ral_classic_2"
|
||||
},
|
||||
"2013": {
|
||||
"code": "2013",
|
||||
"name": "Перламутрово-оранжевый",
|
||||
"hex": "#954527",
|
||||
"groupId": "ral_classic_2"
|
||||
},
|
||||
"3000": {
|
||||
"code": "3000",
|
||||
"name": "Огненно-красный",
|
||||
|
@ -377,18 +342,6 @@ export const ralClassicPallette = {
|
|||
"hex": "#D56D56",
|
||||
"groupId": "ral_classic_3"
|
||||
},
|
||||
"3024": {
|
||||
"code": "3024",
|
||||
"name": "Люминесцентный красный",
|
||||
"hex": "#F70000",
|
||||
"groupId": "ral_classic_3"
|
||||
},
|
||||
"3026": {
|
||||
"code": "3026",
|
||||
"name": "Люминесцентный ярко-красный",
|
||||
"hex": "#FF0000",
|
||||
"groupId": "ral_classic_3"
|
||||
},
|
||||
"3027": {
|
||||
"code": "3027",
|
||||
"name": "Малиново-красный",
|
||||
|
@ -407,18 +360,6 @@ export const ralClassicPallette = {
|
|||
"hex": "#AC323B",
|
||||
"groupId": "ral_classic_3"
|
||||
},
|
||||
"3032": {
|
||||
"code": "3032",
|
||||
"name": "Перламутрово-рубиновый",
|
||||
"hex": "#711521",
|
||||
"groupId": "ral_classic_3"
|
||||
},
|
||||
"3033": {
|
||||
"code": "3033",
|
||||
"name": "Перламутрово-розовый",
|
||||
"hex": "#B24C43",
|
||||
"groupId": "ral_classic_3"
|
||||
},
|
||||
"4001": {
|
||||
"code": "4001",
|
||||
"name": "Красно-сиреневый",
|
||||
|
@ -479,18 +420,6 @@ export const ralClassicPallette = {
|
|||
"hex": "#C63678",
|
||||
"groupId": "ral_classic_4"
|
||||
},
|
||||
"4011": {
|
||||
"code": "4011",
|
||||
"name": "Перламутрово-фиолетовый",
|
||||
"hex": "#8773A1",
|
||||
"groupId": "ral_classic_4"
|
||||
},
|
||||
"4012": {
|
||||
"code": "4012",
|
||||
"name": "Перламутрово-ежевичный",
|
||||
"hex": "#6B6880",
|
||||
"groupId": "ral_classic_4"
|
||||
},
|
||||
"5000": {
|
||||
"code": "5000",
|
||||
"name": "Фиолетово-синий",
|
||||
|
@ -629,18 +558,6 @@ export const ralClassicPallette = {
|
|||
"hex": "#6A93B0",
|
||||
"groupId": "ral_classic_5"
|
||||
},
|
||||
"5025": {
|
||||
"code": "5025",
|
||||
"name": "Перламутровый горечавково-синий",
|
||||
"hex": "#296478",
|
||||
"groupId": "ral_classic_5"
|
||||
},
|
||||
"5026": {
|
||||
"code": "5026",
|
||||
"name": "Перламутровый ночной синий",
|
||||
"hex": "#102C54",
|
||||
"groupId": "ral_classic_5"
|
||||
},
|
||||
"6000": {
|
||||
"code": "6000",
|
||||
"name": "Патиново-зелёный",
|
||||
|
@ -833,30 +750,13 @@ export const ralClassicPallette = {
|
|||
"hex": "#7FB0B2",
|
||||
"groupId": "ral_classic_6"
|
||||
},
|
||||
"6035": {
|
||||
"code": "6035",
|
||||
"name": "Перламутрово-зелёный",
|
||||
"hex": "#1B542C",
|
||||
"groupId": "ral_classic_6"
|
||||
},
|
||||
"6036": {
|
||||
"code": "6036",
|
||||
"name": "Перламутровый опаловый зелёный",
|
||||
"hex": "#005D4C",
|
||||
"groupId": "ral_classic_6"
|
||||
},
|
||||
|
||||
"6037": {
|
||||
"code": "6037",
|
||||
"name": "Зелёный",
|
||||
"hex": "#008F39",
|
||||
"groupId": "ral_classic_6"
|
||||
},
|
||||
"6038": {
|
||||
"code": "6038",
|
||||
"name": "Люминесцентный зелёный",
|
||||
"hex": "#00BB2E",
|
||||
"groupId": "ral_classic_6"
|
||||
},
|
||||
"7000": {
|
||||
"code": "7000",
|
||||
"name": "Серая белка",
|
||||
|
@ -1079,12 +979,7 @@ export const ralClassicPallette = {
|
|||
"hex": "#CFD0CF",
|
||||
"groupId": "ral_classic_7"
|
||||
},
|
||||
"7048": {
|
||||
"code": "7048",
|
||||
"name": "Перламутровый мышино-серый",
|
||||
"hex": "#888175",
|
||||
"groupId": "ral_classic_7"
|
||||
},
|
||||
|
||||
"8000": {
|
||||
"code": "8000",
|
||||
"name": "Зелёно-коричневый",
|
||||
|
@ -1199,12 +1094,7 @@ export const ralClassicPallette = {
|
|||
"hex": "#4E3B2B",
|
||||
"groupId": "ral_classic_8"
|
||||
},
|
||||
"8029": {
|
||||
"code": "8029",
|
||||
"name": "Перламутровый медный",
|
||||
"hex": "#773C27",
|
||||
"groupId": "ral_classic_8"
|
||||
},
|
||||
|
||||
"9001": {
|
||||
"code": "9001",
|
||||
"name": "Кремово-белый",
|
||||
|
@ -1235,18 +1125,6 @@ export const ralClassicPallette = {
|
|||
"hex": "#0A0A0D",
|
||||
"groupId": "ral_classic_9"
|
||||
},
|
||||
"9006": {
|
||||
"code": "9006",
|
||||
"name": "Бело-алюминиевый",
|
||||
"hex": "#A5A8A6",
|
||||
"groupId": "ral_classic_9"
|
||||
},
|
||||
"9007": {
|
||||
"code": "9007",
|
||||
"name": "Тёмно-алюминиевый",
|
||||
"hex": "#8F8F8C",
|
||||
"groupId": "ral_classic_9"
|
||||
},
|
||||
"9010": {
|
||||
"code": "9010",
|
||||
"name": "Белый",
|
||||
|
@ -1277,18 +1155,6 @@ export const ralClassicPallette = {
|
|||
"hex": "#CFD3CD",
|
||||
"groupId": "ral_classic_9"
|
||||
},
|
||||
"9022": {
|
||||
"code": "9022",
|
||||
"name": "Перламутровый светло-серый",
|
||||
"hex": "#9C9C9C",
|
||||
"groupId": "ral_classic_9"
|
||||
},
|
||||
"9023": {
|
||||
"code": "9023",
|
||||
"name": "Перламутровый тёмно-серый",
|
||||
"hex": "#7E8182",
|
||||
"groupId": "ral_classic_9"
|
||||
}
|
||||
}
|
||||
export type ralTypes = keyof typeof ralClassicPallette
|
||||
export const getColorHexFromRal = (col: keyof typeof ralClassicPallette) => {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
export const toppers = [
|
||||
{ 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)
|
||||
if (!el || !el.filename) return undefined
|
||||
return `/topper/${el?.filename}`
|
||||
}
|
||||
|
||||
export const getName = (id: toppersIds) => {
|
||||
const el = toppers.find(el => 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)
|
||||
if (!el || !el.filename) return undefined
|
||||
return `/models_one/pillar/topper/${el?.model}.glb`
|
||||
}
|
||||
|
||||
export type toppersIds = typeof toppers[number]['id']
|
|
@ -1,20 +1,31 @@
|
|||
import type { ralTypes } from '@/components/ral'
|
||||
import { Vector3 } from 'three'
|
||||
import { type patternIds } from '~/components/pattern'
|
||||
import { type toppersIds } from '~/components/topper'
|
||||
|
||||
export const predefPillarColors = ['3004', '7043', '6028', '5013', '8016', '1020', '3005', '4009']
|
||||
export const predefLamelleColors = ['3009', '9003', '6027', '5024', '9001', '1012', '3007', '4007']
|
||||
|
||||
const n = 2
|
||||
const min = 1300
|
||||
const n = 4
|
||||
const min = 1370
|
||||
|
||||
export const use_lamelle_height = () => useState<number>('lamelle_height', () => 0.115)
|
||||
export const use_lamelles_count = () => useState('lamelles_count', () => 14)
|
||||
export const use_fence_section = () => useState<number>('fence_section', () => min * 0.001)
|
||||
|
||||
export const use_remove_pillar = () => useState<boolean>('remove_pillar', () => false)
|
||||
export const use_pillar_color = () => useState<ralTypes>('pillar_color')
|
||||
export const use_lamelle_color = () => useState<ralTypes>('lamelle_color')
|
||||
export const use_pattern = () => useState<patternIds>('pattern', () => 0)
|
||||
export const use_topper = () => useState<toppersIds>('topper', () => 0)
|
||||
|
||||
export const use_section_count = () => useState('section_count', () => n)
|
||||
export const use_extra_section = () => useState('extra_section', () => 0)
|
||||
export const use_total_length = () => useState('total_length', () => (((min + 104) * n) + 104) * 0.001)
|
||||
export const use_min_length = () => useState('min_length', () => 700)
|
||||
export const use_max_size = () => useState<number>('max_size', () => 20)
|
||||
export const use_max_size = () => useState<number>('max_size', () => 1)
|
||||
export const use_explosion_state = () => useState<boolean>('explosion_state', () => false)
|
||||
|
||||
export const use_goto_camera = () => useState<Vector3 | undefined>('gotocam', () => undefined)
|
||||
export const use_goto_target = () => useState<Vector3 | undefined>('gotocontrols', () => undefined)
|
||||
export const use_open_calc = () => useState<string[]>('open_calc', () => [])
|
|
@ -1,6 +1,7 @@
|
|||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
|
||||
export default defineNuxtConfig({
|
||||
devtools: { enabled: false },
|
||||
hooks: {},
|
||||
app: {
|
||||
pageTransition: { name: 'page', mode: 'out-in' },
|
||||
|
@ -45,6 +46,7 @@ export default defineNuxtConfig({
|
|||
vite: {
|
||||
assetsInclude: ['**/*.glb', '**/*.gltf'],
|
||||
build: {
|
||||
target: 'esnext'
|
||||
// minify: 'esbuild'
|
||||
// minify: false
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"dependencies": {
|
||||
"@artmizu/yandex-metrika-nuxt": "^1.0.4",
|
||||
"@monogrid/gainmap-js": "^3.0.5",
|
||||
"@nodetoy/three-nodetoy": "^0.1.36",
|
||||
"@nuxt/image": "^1.7.0",
|
||||
"@nuxtjs/robots": "^4.0.0",
|
||||
"@nuxtjs/sitemap": "^5.3.5",
|
||||
|
@ -20,12 +21,14 @@
|
|||
"@tresjs/cientos": "^3.9.0",
|
||||
"@tresjs/core": "^4.0.2",
|
||||
"@tresjs/nuxt": "^2.1.2",
|
||||
"@tresjs/post-processing": "^0.7.1",
|
||||
"consola": "^3.2.3",
|
||||
"marked": "^12.0.2",
|
||||
"nuxt": "^3.11.2",
|
||||
"nuxt-icon": "^0.6.10",
|
||||
"nuxt-svgo": "^4.0.1",
|
||||
"sass": "^1.77.4",
|
||||
"sharp": "^0.33.5",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"three": "^0.165.0",
|
||||
"vue": "^3.4.27",
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div class="siteblock siteblock_calc bg-white">
|
||||
<div class="container">
|
||||
<div class="col-span-12 xl:col-span-9 h-full relative">
|
||||
<Suspense>
|
||||
<LazyCalcModels />
|
||||
</Suspense>
|
||||
</div>
|
||||
<div class="col-span-12 xl:col-span-3">
|
||||
<CalcValues />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -107,10 +107,16 @@ const { data: advData } = await apiFetch<ApiAdvantageType[]>(`advantage/`)
|
|||
alt="коричневый забор" title="" format="webp" loading="lazy" />
|
||||
</div>
|
||||
<div class="siteblock siteblock_calc bg-white">
|
||||
<LazyCalcValues />
|
||||
<Suspense>
|
||||
<LazyCalcModels />
|
||||
</Suspense>
|
||||
<div class="container">
|
||||
<div class="col-span-12 xl:col-span-9 h-full relative">
|
||||
<Suspense>
|
||||
<LazyCalcModels />
|
||||
</Suspense>
|
||||
</div>
|
||||
<div class="col-span-12 xl:col-span-3">
|
||||
<CalcValues />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="siteblock bg-white siteblock_content" :id="delivery?.slug" v-if="deliveryText">
|
||||
<div class="container">
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
stroke-width: 0px;
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<g>
|
||||
<path class="cls-1" d="M256,154.83c-2.56,4.22-5.21,8.38-7.97,12.46-24.26,35.89-56.06,66.22-93.21,88.72,4.22,2.56,8.38,5.21,12.46,7.97,35.89,24.26,66.22,56.06,88.72,93.21,2.56-4.22,5.21-8.38,7.97-12.46,24.26-35.89,56.06-66.22,93.21-88.72-4.22-2.56-8.38-5.21-12.46-7.97-35.89-24.26-66.22-56.06-88.72-93.21Z"/>
|
||||
<path class="cls-1" d="M137.02,79.47c-33.99,22.96-61.04,55.55-77.17,93.69-4.62,10.93-8.35,22.31-11.1,34.08,4.92-1.15,9.78-2.46,14.56-3.95,19.96-6.21,38.67-15.28,55.67-26.76,33.99-22.96,61.04-55.55,77.17-93.69,4.62-10.93,8.35-22.31,11.1-34.08-4.92,1.15-9.78,2.46-14.56,3.95-19.96,6.21-38.67,15.28-55.67,26.76Z"/>
|
||||
<path class="cls-1" d="M315.86,429.16c-4.62,10.93-8.35,22.31-11.1,34.08,4.92-1.15,9.78-2.46,14.56-3.95,19.96-6.21,38.67-15.28,55.67-26.76,33.99-22.96,61.04-55.55,77.17-93.69,4.62-10.93,8.35-22.31,11.1-34.08-4.92,1.15-9.78,2.46-14.56,3.95-19.96,6.21-38.67,15.28-55.67,26.76-33.99,22.96-61.04,55.55-77.17,93.69Z"/>
|
||||
<path class="cls-1" d="M432.53,137.02c-22.96-33.99-55.55-61.04-93.69-77.17-10.93-4.62-22.31-8.35-34.08-11.1,1.15,4.92,2.46,9.78,3.95,14.56,6.21,19.96,15.28,38.67,26.76,55.67,22.96,33.99,55.55,61.04,93.69,77.17,10.93,4.62,22.31,8.35,34.08,11.1-1.15-4.92-2.46-9.78-3.95-14.56-6.21-19.96-15.28-38.67-26.76-55.67Z"/>
|
||||
<path class="cls-1" d="M0,410.83c22.5,37.14,52.82,68.95,88.71,93.2,4.08,2.76,8.24,5.41,12.47,7.97H0v-101.17Z"/>
|
||||
<path class="cls-1" d="M410.83,512h101.17v-101.18c-2.56,4.22-5.21,8.38-7.97,12.47-24.25,35.89-56.06,66.21-93.2,88.71Z"/>
|
||||
<path class="cls-1" d="M512,101.17c-22.5-37.14-52.82-68.95-88.71-93.2C419.2,5.21,415.04,2.56,410.82,0h101.18s0,101.17,0,101.17Z"/>
|
||||
<path class="cls-1" d="M101.17,0H0v101.18c2.56-4.22,5.21-8.38,7.97-12.47C32.22,52.82,64.03,22.5,101.17,0Z"/>
|
||||
<path class="cls-1" d="M79.47,374.98c22.96,33.99,55.55,61.04,93.69,77.17,10.93,4.62,22.31,8.35,34.08,11.1-1.15-4.92-2.46-9.78-3.95-14.56-6.21-19.96-15.28-38.67-26.76-55.67-22.96-33.99-55.55-61.04-93.69-77.17-10.93-4.62-22.31-8.35-34.08-11.1,1.15,4.92,2.46,9.78,3.95,14.56,6.21,19.96,15.28,38.67,26.76,55.67Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 124 KiB |
After Width: | Height: | Size: 182 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg width="147" height="129" viewBox="0 0 147 129" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.092 0.0210266L73.148 0L126.204 0.0210266L129.064 3.06403L128.838 14.843L146.236 22.519L146.296 37.488L140.375 47.003L132.706 47.026V128.971L73.148 128.983L13.59 128.971V47.026L5.92099 47.003L0 37.488L0.0599976 22.519L17.458 14.843L17.232 3.06403L20.092 0.0210266Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 440 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="117" height="135" viewBox="0 0 117 135" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M56.541 134.891H60.328H64.115H67.902H103.205H116.867V38.407H103.205L67.81 3.65302L64.454 1.259L60.508 0H56.359L52.413 1.259L49.057 3.65302L13.662 38.407H0V134.891H13.662H48.965H52.752H56.541Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 364 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="117" height="130" viewBox="0 0 117 130" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M116.868 129.438V0H0V129.438H116.868Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 210 B |
|
@ -0,0 +1,3 @@
|
|||
#include <dithering_fragment>
|
||||
|
||||
// gl_FragColor = vec4(normal, 1.0);
|
|
@ -0,0 +1,4 @@
|
|||
float noise = pnoise( 1000.0 * vPosition, vec3( 1000.0 ) );
|
||||
vec3 modifiedNormal = normal - vec3(noise) * 0.05;
|
||||
// modifiedNormal = vec3(noise);
|
||||
normal = vec3(modifiedNormal);
|
|
@ -0,0 +1,171 @@
|
|||
//
|
||||
// GLSL textureless classic 3D noise "cnoise",
|
||||
// with an RSL-style periodic variant "pnoise".
|
||||
// Author: Stefan Gustavson (stefan.gustavson@liu.se)
|
||||
// Version: 2011-10-11
|
||||
//
|
||||
// Many thanks to Ian McEwan of Ashima Arts for the
|
||||
// ideas for permutation and gradient selection.
|
||||
//
|
||||
// Copyright (c) 2011 Stefan Gustavson. All rights reserved.
|
||||
// Distributed under the MIT license. See LICENSE file.
|
||||
// https://github.com/stegu/webgl-noise
|
||||
//
|
||||
|
||||
vec3 mod289(vec3 x) {
|
||||
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||
}
|
||||
|
||||
vec4 mod289(vec4 x) {
|
||||
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||
}
|
||||
|
||||
vec4 permute(vec4 x) {
|
||||
return mod289(((x * 34.0) + 10.0) * x);
|
||||
}
|
||||
|
||||
vec4 taylorInvSqrt(vec4 r) {
|
||||
return 1.79284291400159 - 0.85373472095314 * r;
|
||||
}
|
||||
|
||||
vec3 fade(vec3 t) {
|
||||
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
|
||||
}
|
||||
|
||||
// Classic Perlin noise
|
||||
float cnoise(vec3 P) {
|
||||
vec3 Pi0 = floor(P); // Integer part for indexing
|
||||
vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1
|
||||
Pi0 = mod289(Pi0);
|
||||
Pi1 = mod289(Pi1);
|
||||
vec3 Pf0 = fract(P); // Fractional part for interpolation
|
||||
vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
|
||||
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
|
||||
vec4 iy = vec4(Pi0.yy, Pi1.yy);
|
||||
vec4 iz0 = Pi0.zzzz;
|
||||
vec4 iz1 = Pi1.zzzz;
|
||||
|
||||
vec4 ixy = permute(permute(ix) + iy);
|
||||
vec4 ixy0 = permute(ixy + iz0);
|
||||
vec4 ixy1 = permute(ixy + iz1);
|
||||
|
||||
vec4 gx0 = ixy0 * (1.0 / 7.0);
|
||||
vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
|
||||
gx0 = fract(gx0);
|
||||
vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
|
||||
vec4 sz0 = step(gz0, vec4(0.0));
|
||||
gx0 -= sz0 * (step(0.0, gx0) - 0.5);
|
||||
gy0 -= sz0 * (step(0.0, gy0) - 0.5);
|
||||
|
||||
vec4 gx1 = ixy1 * (1.0 / 7.0);
|
||||
vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
|
||||
gx1 = fract(gx1);
|
||||
vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
|
||||
vec4 sz1 = step(gz1, vec4(0.0));
|
||||
gx1 -= sz1 * (step(0.0, gx1) - 0.5);
|
||||
gy1 -= sz1 * (step(0.0, gy1) - 0.5);
|
||||
|
||||
vec3 g000 = vec3(gx0.x, gy0.x, gz0.x);
|
||||
vec3 g100 = vec3(gx0.y, gy0.y, gz0.y);
|
||||
vec3 g010 = vec3(gx0.z, gy0.z, gz0.z);
|
||||
vec3 g110 = vec3(gx0.w, gy0.w, gz0.w);
|
||||
vec3 g001 = vec3(gx1.x, gy1.x, gz1.x);
|
||||
vec3 g101 = vec3(gx1.y, gy1.y, gz1.y);
|
||||
vec3 g011 = vec3(gx1.z, gy1.z, gz1.z);
|
||||
vec3 g111 = vec3(gx1.w, gy1.w, gz1.w);
|
||||
|
||||
vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
|
||||
g000 *= norm0.x;
|
||||
g010 *= norm0.y;
|
||||
g100 *= norm0.z;
|
||||
g110 *= norm0.w;
|
||||
vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
|
||||
g001 *= norm1.x;
|
||||
g011 *= norm1.y;
|
||||
g101 *= norm1.z;
|
||||
g111 *= norm1.w;
|
||||
|
||||
float n000 = dot(g000, Pf0);
|
||||
float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
|
||||
float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
|
||||
float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
|
||||
float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
|
||||
float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
|
||||
float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
|
||||
float n111 = dot(g111, Pf1);
|
||||
|
||||
vec3 fade_xyz = fade(Pf0);
|
||||
vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
|
||||
vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
|
||||
float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
|
||||
return 2.2 * n_xyz;
|
||||
}
|
||||
|
||||
// Classic Perlin noise, periodic variant
|
||||
float pnoise(vec3 P, vec3 rep) {
|
||||
vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period
|
||||
vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period
|
||||
Pi0 = mod289(Pi0);
|
||||
Pi1 = mod289(Pi1);
|
||||
vec3 Pf0 = fract(P); // Fractional part for interpolation
|
||||
vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
|
||||
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
|
||||
vec4 iy = vec4(Pi0.yy, Pi1.yy);
|
||||
vec4 iz0 = Pi0.zzzz;
|
||||
vec4 iz1 = Pi1.zzzz;
|
||||
|
||||
vec4 ixy = permute(permute(ix) + iy);
|
||||
vec4 ixy0 = permute(ixy + iz0);
|
||||
vec4 ixy1 = permute(ixy + iz1);
|
||||
|
||||
vec4 gx0 = ixy0 * (1.0 / 7.0);
|
||||
vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
|
||||
gx0 = fract(gx0);
|
||||
vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
|
||||
vec4 sz0 = step(gz0, vec4(0.0));
|
||||
gx0 -= sz0 * (step(0.0, gx0) - 0.5);
|
||||
gy0 -= sz0 * (step(0.0, gy0) - 0.5);
|
||||
|
||||
vec4 gx1 = ixy1 * (1.0 / 7.0);
|
||||
vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
|
||||
gx1 = fract(gx1);
|
||||
vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
|
||||
vec4 sz1 = step(gz1, vec4(0.0));
|
||||
gx1 -= sz1 * (step(0.0, gx1) - 0.5);
|
||||
gy1 -= sz1 * (step(0.0, gy1) - 0.5);
|
||||
|
||||
vec3 g000 = vec3(gx0.x, gy0.x, gz0.x);
|
||||
vec3 g100 = vec3(gx0.y, gy0.y, gz0.y);
|
||||
vec3 g010 = vec3(gx0.z, gy0.z, gz0.z);
|
||||
vec3 g110 = vec3(gx0.w, gy0.w, gz0.w);
|
||||
vec3 g001 = vec3(gx1.x, gy1.x, gz1.x);
|
||||
vec3 g101 = vec3(gx1.y, gy1.y, gz1.y);
|
||||
vec3 g011 = vec3(gx1.z, gy1.z, gz1.z);
|
||||
vec3 g111 = vec3(gx1.w, gy1.w, gz1.w);
|
||||
|
||||
vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
|
||||
g000 *= norm0.x;
|
||||
g010 *= norm0.y;
|
||||
g100 *= norm0.z;
|
||||
g110 *= norm0.w;
|
||||
vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
|
||||
g001 *= norm1.x;
|
||||
g011 *= norm1.y;
|
||||
g101 *= norm1.z;
|
||||
g111 *= norm1.w;
|
||||
|
||||
float n000 = dot(g000, Pf0);
|
||||
float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
|
||||
float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
|
||||
float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
|
||||
float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
|
||||
float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
|
||||
float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
|
||||
float n111 = dot(g111, Pf1);
|
||||
|
||||
vec3 fade_xyz = fade(Pf0);
|
||||
vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
|
||||
vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
|
||||
float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
|
||||
return 2.2 * n_xyz;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
vec3 scale;
|
||||
scale.x = length(modelMatrix[0].xyz);
|
||||
scale.y = length(modelMatrix[1].xyz);
|
||||
scale.z = length(modelMatrix[2].xyz);
|
||||
|
||||
// Применяем масштаб к позиции вершины
|
||||
vPosition = (position.xyz + normal) * scale;
|
|
@ -0,0 +1,18 @@
|
|||
import { Color } from 'three';
|
||||
import { type ralTypes } from './../components/ral';
|
||||
import { getColorHexFromRal } from "~/components/ral"
|
||||
|
||||
export const contrastColor = (color: ralTypes) => {
|
||||
const hex = getColorHexFromRal(color)
|
||||
if (hex) {
|
||||
const hsl = { h: 0, s: 0, l: 0 }
|
||||
new Color(hex).getHSL(hsl)
|
||||
if (hsl.l < 0.45) {
|
||||
return '#fff'
|
||||
} else {
|
||||
return '#000'
|
||||
}
|
||||
} else {
|
||||
return '#000'
|
||||
}
|
||||
}
|
|
@ -1,23 +1,115 @@
|
|||
import { Color, MeshStandardMaterial } from "three"
|
||||
import {
|
||||
Color, DoubleSide,
|
||||
MeshStandardMaterial, RepeatWrapping,
|
||||
Texture, TextureLoader,
|
||||
type WebGLProgramParameters
|
||||
} from "three"
|
||||
import { useLoader, } from '@tresjs/core'
|
||||
import { getFilename, patterns, type patternIds, } from "~/components/pattern"
|
||||
|
||||
import vertexShader from '../shaders/noise/vertex.vert?raw'
|
||||
import normalShader from '../shaders/noise/normal.frag?raw'
|
||||
import dithShader from '../shaders/noise/dith.frag?raw'
|
||||
import noiseShader from '../shaders/noise/pnoise.glsl?raw'
|
||||
|
||||
const set_metaril_func = (scene: any, material: any) => {
|
||||
scene.children.forEach((el: any) => {
|
||||
if (el.isMesh && !el.isInstancedMesh) {
|
||||
if (el.isMesh) {
|
||||
el.castShadow = true
|
||||
el.receiveShadow = true
|
||||
}
|
||||
if (el.material) el.material = material
|
||||
el.material = material
|
||||
set_metaril_func(el, material)
|
||||
})
|
||||
}
|
||||
export const set_material = (scene: any, color: any) => {
|
||||
let c = color
|
||||
const material = new MeshStandardMaterial({
|
||||
color: new Color(c || '#9c9c00'),
|
||||
transparent: true,
|
||||
opacity: 1,
|
||||
roughness: 0.5,
|
||||
metalness: 0
|
||||
})
|
||||
set_metaril_func(scene, material)
|
||||
|
||||
const loaded_patterns: { [key: string]: any } = {}
|
||||
for (let index = 0; index < patterns.length; index++) {
|
||||
const element = patterns[index];
|
||||
const filename = getFilename(element.id)
|
||||
if (filename) {
|
||||
// @ts-ignore
|
||||
loaded_patterns[filename] = useLoader(TextureLoader, filename)
|
||||
}
|
||||
}
|
||||
Promise.all(Object.keys(loaded_patterns))
|
||||
|
||||
const noiseMaterial = new MeshStandardMaterial({
|
||||
// alphaMap: pattern ? texture : null,
|
||||
transparent: true,
|
||||
opacity: 1,
|
||||
roughness: 0.2,
|
||||
metalness: 0,
|
||||
side: DoubleSide,
|
||||
})
|
||||
|
||||
const m_onBeforeCompile = (shader: WebGLProgramParameters) => {
|
||||
// Изменяем вершинный шейдер
|
||||
shader.vertexShader = `
|
||||
varying vec3 vPosition;
|
||||
${shader.vertexShader}
|
||||
`.replace(
|
||||
`#include <begin_vertex>`,
|
||||
`#include <begin_vertex>
|
||||
${vertexShader}
|
||||
`
|
||||
);
|
||||
|
||||
// Изменяем фрагментный шейдер
|
||||
shader.fragmentShader = `
|
||||
varying vec3 vPosition;
|
||||
|
||||
${noiseShader}
|
||||
${shader.fragmentShader}`
|
||||
.replace(
|
||||
'#include <normal_fragment_begin>',
|
||||
`#include <normal_fragment_begin>
|
||||
${normalShader}
|
||||
`
|
||||
)
|
||||
.replace(
|
||||
`#include <dithering_fragment>`,
|
||||
`#include <dithering_fragment>
|
||||
|
||||
${dithShader}
|
||||
`
|
||||
);
|
||||
};
|
||||
export const set_material = (
|
||||
scene: any,
|
||||
color: any,
|
||||
pattern: { pattern: patternIds, count: number } | undefined = undefined,
|
||||
noise_material: boolean = false,
|
||||
) => {
|
||||
let c = color
|
||||
const material = noiseMaterial.clone()
|
||||
material.color = new Color(c || '#9c9c00')
|
||||
if (noise_material) {
|
||||
material.onBeforeCompile = m_onBeforeCompile
|
||||
}
|
||||
|
||||
const promises = []
|
||||
if (pattern && pattern.pattern !== undefined) {
|
||||
const filename = getFilename(pattern.pattern)
|
||||
if (filename) {
|
||||
const texture = loaded_patterns[filename]
|
||||
promises.push(texture)
|
||||
texture.then((res: Texture) => {
|
||||
res.wrapT = RepeatWrapping;
|
||||
res.repeat.set(1, pattern.count);
|
||||
res.needsUpdate = true
|
||||
material.alphaMap = res
|
||||
scene.renderOrder = 0
|
||||
return res
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (scene) set_metaril_func(scene, material)
|
||||
else console.log(scene)
|
||||
|
||||
Promise.all(promises).then((values) => {
|
||||
if (scene) set_metaril_func(scene, material)
|
||||
else console.log(scene)
|
||||
});
|
||||
}
|