Merge pull request 'dev' (#84) from dev into main
Deploy / build_and_push_images (push) Successful in 1m31s Details
Deploy / deploy_to_server_dev (push) Successful in 39s Details

Reviewed-on: #84
This commit is contained in:
ksenia_mikhailova 2024-10-03 15:30:22 +03:00
commit 1c74647a21
62 changed files with 2338 additions and 888 deletions

View File

@ -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;

View File

@ -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>

281
components/calc/values.vue Normal file
View File

@ -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) }}&nbsp;мм</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 }}&nbsp;мм</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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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()

48
components/model/env.vue Normal file
View File

@ -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>

View File

@ -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>

View File

@ -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

92
components/model/line.vue Normal file
View File

@ -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>

View File

@ -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>

View File

@ -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>

19
components/pattern.ts Normal file
View File

@ -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']

View File

@ -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>

View File

@ -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) => {

24
components/topper.ts Normal file
View File

@ -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']

View File

@ -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', () => [])

View File

@ -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
},

901
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

14
pages/calc.vue Normal file
View File

@ -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>

View File

@ -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">

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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

BIN
public/pattern/tile1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
public/pattern/tile2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
public/texture/images.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/texture/normal.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View File

@ -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

View File

@ -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

View File

@ -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

3
shaders/noise/dith.frag Normal file
View File

@ -0,0 +1,3 @@
#include <dithering_fragment>
// gl_FragColor = vec4(normal, 1.0);

View File

@ -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);

171
shaders/noise/pnoise.glsl Normal file
View File

@ -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;
}

View File

@ -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;

18
utils/contrast_color.ts Normal file
View File

@ -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'
}
}

View File

@ -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)
});
}