dev #10

Merged
ksenia_mikhailova merged 46 commits from dev into main 2024-07-24 12:58:58 +03:00
16 changed files with 8651 additions and 448 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,9 @@
from django.contrib import admin
from .models import ClickableArea, Element3D, Scene3D
admin.site.register(Scene3D)
class Scene3DAdmin(admin.ModelAdmin):
filter_horizontal = ('elements',)
admin.site.register(Scene3D, Scene3DAdmin)
admin.site.register(Element3D)
admin.site.register(ClickableArea)

View File

@ -1,5 +1,7 @@
from PIL import Image
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.exceptions import ValidationError
import logging
logger = logging.getLogger("root")
@ -16,13 +18,16 @@ class Element3D(models.Model):
parent = models.ForeignKey("self", on_delete=models.PROTECT, blank=True, null=True)
model_file = models.FileField(upload_to=group_based_upload_to)
name = models.CharField(max_length=255)
description = models.TextField()
description = models.TextField(blank=True, null=True)
is_enabled = models.BooleanField(default=True)
can_not_disable = models.BooleanField(default=False)
def __str__(self):
return self.name
class Scene3D(models.Model):
filter_horizontal = ("elements",)
name = models.CharField(
max_length=120,
)
@ -32,13 +37,30 @@ class Scene3D(models.Model):
validators=[MinValueValidator(1), MaxValueValidator(600)], blank=True, null=True
)
max_distance = models.IntegerField(
validators=[MinValueValidator(2), MaxValueValidator(1000)], blank=True, null=True
validators=[MinValueValidator(2), MaxValueValidator(1000)],
blank=True,
null=True,
)
hdr_gainmap = models.FileField(
upload_to=group_based_upload_to, blank=True, null=True
)
hdr_json = models.FileField(upload_to=group_based_upload_to, blank=True, null=True)
hdr_webp = models.FileField(upload_to=group_based_upload_to, blank=True, null=True)
def __str__(self):
return self.name
def maximum_size_validator(image):
max_width = 512
max_height = 512
img = Image.open(image)
fw, fh = img.size
if fw > max_width or fh > max_height:
raise ValidationError("Height or Width is larger than what is allowed")
class ClickableArea(models.Model):
name = models.CharField("название", max_length=255)
description = models.TextField("описание")
@ -59,6 +81,15 @@ class ClickableArea(models.Model):
Element3D,
on_delete=models.PROTECT,
)
image = models.ImageField(
"Картинка",
upload_to=group_based_upload_to,
validators=[
maximum_size_validator,
],
blank=True,
null=True,
)
def __str__(self):
return self.name

View File

@ -12,6 +12,9 @@ class Element3DSerializer(serializers.ModelSerializer):
class Scene3DSerializer(serializers.ModelSerializer):
elements = Element3DSerializer(many=True)
hdr_gainmap = serializers.FileField(use_url=False)
hdr_json = serializers.FileField(use_url=False)
hdr_webp = serializers.FileField(use_url=False)
class Meta:
model = Scene3D
@ -20,6 +23,8 @@ class Scene3DSerializer(serializers.ModelSerializer):
class ClickableAreaSerializer(serializers.ModelSerializer):
image = serializers.ImageField(use_url=False)
class Meta:
model = ClickableArea
fields = "__all__"

View File

@ -1,3 +1,3 @@
# VITE_SERVER_URL='http://localhost:8000'
VITE_SERVER_URL='https://demo.kustarshina.ru'
VITE_IMAGE_URL='https://demo.kustarshina.ru'
VITE_SERVER_URL='http://localhost:8000'

View File

@ -17,6 +17,7 @@ declare module 'vue' {
IMdiHexagonOutline: typeof import('~icons/mdi/hexagon-outline')['default']
IMdiHome: typeof import('~icons/mdi/home')['default']
IMdiMonitorScreenshot: typeof import('~icons/mdi/monitor-screenshot')['default']
IMdiPagePreviousOutline: typeof import('~icons/mdi/page-previous-outline')['default']
IMdiShop: typeof import('~icons/mdi/shop')['default']
IMdiVideo3d: typeof import('~icons/mdi/video3d')['default']
Item: typeof import('./src/components/Floorplan/item.vue')['default']

176
front/package-lock.json generated
View File

@ -11,7 +11,9 @@
"@fireworks-js/vue": "^2.10.7",
"@monogrid/gainmap-js": "^3.0.5",
"@tresjs/cientos": "^3.9.0",
"@tresjs/core": "^3.9.0",
"@tresjs/core": "^4.0.2",
"@tresjs/leches": "^0.14.0",
"@tresjs/post-processing": "^0.7.1",
"@vueuse/components": "^10.9.0",
"@vueuse/core": "^10.9.0",
"d3": "^7.9.0",
@ -84,7 +86,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
@ -100,7 +101,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
@ -116,7 +116,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
@ -132,7 +131,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
@ -148,7 +146,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
@ -164,7 +161,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
@ -180,7 +176,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
@ -196,7 +191,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
@ -212,7 +206,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -228,7 +221,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -244,7 +236,6 @@
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -260,7 +251,6 @@
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -276,7 +266,6 @@
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -292,7 +281,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -308,7 +296,6 @@
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -324,7 +311,6 @@
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -340,7 +326,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -356,7 +341,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
@ -372,7 +356,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
@ -388,7 +371,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
@ -404,7 +386,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
@ -420,7 +401,6 @@
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
@ -436,7 +416,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
@ -594,7 +573,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
@ -607,7 +585,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
@ -620,7 +597,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
@ -633,7 +609,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
@ -646,7 +621,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -659,7 +633,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -672,7 +645,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -685,7 +657,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -698,7 +669,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -711,7 +681,6 @@
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -724,7 +693,6 @@
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -737,7 +705,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -750,7 +717,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -763,7 +729,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
@ -776,7 +741,6 @@
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
@ -789,7 +753,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
@ -815,7 +778,7 @@
"vue": ">=3.3"
}
},
"node_modules/@tresjs/core": {
"node_modules/@tresjs/cientos/node_modules/@tresjs/core": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/@tresjs/core/-/core-3.9.0.tgz",
"integrity": "sha512-6el70oXSduTvSA3XCI8/rQV2GzcgCLceZKA443CTU/MBPmRDULJ12q/UYl18Ij4CJ68rTqgVi0Da+WNMrs784A==",
@ -829,6 +792,20 @@
"vue": ">=3.3"
}
},
"node_modules/@tresjs/core": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@tresjs/core/-/core-4.0.2.tgz",
"integrity": "sha512-+Shy5ch4m9gQSHRlArZAn4nv2apaFJJv21bAvpOKRXTCtGu0BakKGUpWcTzzmDsTs9t6yndbjCWzyifggjFpQQ==",
"dependencies": {
"@alvarosabu/utils": "^3.2.0",
"@vue/devtools-api": "^6.6.2",
"@vueuse/core": "^10.10.0"
},
"peerDependencies": {
"three": ">=0.133",
"vue": ">=3.4"
}
},
"node_modules/@tresjs/core/node_modules/@vueuse/core": {
"version": "10.10.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.10.0.tgz",
@ -912,6 +889,57 @@
}
}
},
"node_modules/@tresjs/leches": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@tresjs/leches/-/leches-0.14.0.tgz",
"integrity": "sha512-X/UIeldHkx9RZdLuwGh/0XN8uM5JDOjIZH0tUodmXb+AEO/97F90FgSuRyl+xyTTOekh+z3VFavkWpoRS64KVw==",
"dependencies": {
"@unocss/core": "^0.57.7",
"@vueuse/components": "^10.6.1",
"vite-plugin-css-injected-by-js": "^3.3.0"
},
"peerDependencies": {
"vue": ">=3.3.4"
}
},
"node_modules/@tresjs/leches/node_modules/@unocss/core": {
"version": "0.57.7",
"resolved": "https://registry.npmjs.org/@unocss/core/-/core-0.57.7.tgz",
"integrity": "sha512-1d36M0CV3yC80J0pqOa5rH1BX6g2iZdtKmIb3oSBN4AWnMCSrrJEPBrUikyMq2TEQTrYWJIVDzv5A9hBUat3TA==",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@tresjs/post-processing": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/@tresjs/post-processing/-/post-processing-0.7.1.tgz",
"integrity": "sha512-MLrjxR4rJ55MOYFKMEZ8aqh5no2/sOG1fdYk2B49Ntx2p2etyuVpI6e9BBAvrc4M/wktRHkQ2JNDbgMaCiJoMg==",
"dependencies": {
"@tresjs/core": "^3.5.1",
"@unocss/core": "^0.58.0",
"@vueuse/core": "^10.6.1",
"postprocessing": "^6.33.4",
"three-stdlib": "^2.28.7"
},
"peerDependencies": {
"three": ">=0.133",
"vue": ">=3.3"
}
},
"node_modules/@tresjs/post-processing/node_modules/@tresjs/core": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/@tresjs/core/-/core-3.9.0.tgz",
"integrity": "sha512-6el70oXSduTvSA3XCI8/rQV2GzcgCLceZKA443CTU/MBPmRDULJ12q/UYl18Ij4CJ68rTqgVi0Da+WNMrs784A==",
"dependencies": {
"@alvarosabu/utils": "^3.1.1",
"@vue/devtools-api": "^6.6.1",
"@vueuse/core": "^10.7.0"
},
"peerDependencies": {
"three": ">=0.133",
"vue": ">=3.3"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@ -1186,8 +1214,7 @@
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/@types/geojson": {
"version": "7946.0.14",
@ -1234,6 +1261,14 @@
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.15.tgz",
"integrity": "sha512-nC9116Gd4N+CqTxqo6gvCfhAMAzgRcfS8ZsciNodHq8uwW4JCVKwhagw8yN0XmC7mHrLnWqniJpoVEiR+72Drw=="
},
"node_modules/@unocss/core": {
"version": "0.58.9",
"resolved": "https://registry.npmjs.org/@unocss/core/-/core-0.58.9.tgz",
"integrity": "sha512-wYpPIPPsOIbIoMIDuH8ihehJk5pAZmyFKXIYO/Kro98GEOFhz6lJoLsy6/PZuitlgp2/TSlubUuWGjHWvp5osw==",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vitejs/plugin-vue": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
@ -1527,7 +1562,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"devOptional": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@ -1546,7 +1581,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"devOptional": true,
"engines": {
"node": ">=8"
},
@ -1572,7 +1607,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"devOptional": true,
"dependencies": {
"fill-range": "^7.1.1"
},
@ -1598,7 +1633,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"devOptional": true,
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@ -2202,7 +2237,6 @@
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@ -2298,7 +2332,7 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"devOptional": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@ -2345,7 +2379,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@ -2380,7 +2413,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"devOptional": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@ -2467,7 +2500,7 @@
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
"integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
"dev": true
"devOptional": true
},
"node_modules/inherits": {
"version": "2.0.4",
@ -2486,7 +2519,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"devOptional": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@ -2510,7 +2543,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"devOptional": true,
"engines": {
"node": ">=0.10.0"
}
@ -2519,7 +2552,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"devOptional": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@ -2531,7 +2564,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"devOptional": true,
"engines": {
"node": ">=0.12.0"
}
@ -2748,7 +2781,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"devOptional": true,
"engines": {
"node": ">=0.10.0"
}
@ -2882,7 +2915,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"devOptional": true,
"engines": {
"node": ">=8.6"
},
@ -2978,6 +3011,17 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postprocessing": {
"version": "6.35.5",
"resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.35.5.tgz",
"integrity": "sha512-nO8pxbiQT7+u3qv/q4LL1x6tstZWuduDlkOLFWQm3vFFKHzAsRMxqwsGAktkHKMSGXVEwTBvT3TKPklNALQheg==",
"engines": {
"node": ">= 0.13.2"
},
"peerDependencies": {
"three": ">= 0.152.0 < 0.166.0"
}
},
"node_modules/potpack": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
@ -3027,7 +3071,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"devOptional": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@ -3076,7 +3120,6 @@
"version": "4.16.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.4.tgz",
"integrity": "sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
},
@ -3144,7 +3187,7 @@
"version": "1.75.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz",
"integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==",
"dev": true,
"devOptional": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@ -3354,7 +3397,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"devOptional": true,
"dependencies": {
"is-number": "^7.0.0"
},
@ -3518,7 +3561,6 @@
"version": "5.2.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz",
"integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==",
"dev": true,
"dependencies": {
"esbuild": "^0.20.1",
"postcss": "^8.4.38",
@ -3569,6 +3611,14 @@
}
}
},
"node_modules/vite-plugin-css-injected-by-js": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.1.tgz",
"integrity": "sha512-9ioqwDuEBxW55gNoWFEDhfLTrVKXEEZgl5adhWmmqa88EQGKfTmexy4v1Rh0pAS6RhKQs2bUYQArprB32JpUZQ==",
"peerDependencies": {
"vite": ">2.0.0-0"
}
},
"node_modules/vite-plugin-static-copy": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-1.0.5.tgz",

View File

@ -13,7 +13,9 @@
"@fireworks-js/vue": "^2.10.7",
"@monogrid/gainmap-js": "^3.0.5",
"@tresjs/cientos": "^3.9.0",
"@tresjs/core": "^3.9.0",
"@tresjs/core": "^4.0.2",
"@tresjs/leches": "^0.14.0",
"@tresjs/post-processing": "^0.7.1",
"@vueuse/components": "^10.9.0",
"@vueuse/core": "^10.9.0",
"d3": "^7.9.0",

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { onMounted } from 'vue';
import { onMounted, watch } from 'vue';
import { PMREMGenerator } from 'three';
import { EquirectangularReflectionMapping, ReinhardToneMapping } from 'three';
import { GainMapLoader } from '@monogrid/gainmap-js'
import { useTresContext } from '@tresjs/core';
@ -9,20 +9,27 @@ import hdr_gainmap from '../../assets/promo/hdr/hdr-gainmap.webp'
import hdr_json from '../../assets/promo/hdr/hdr.json?url'
import hdr_webp from '../../assets/promo/hdr/hdr.webp'
const props = defineProps(['hdr_webp', 'hdr_gainmap', 'hdr_json'])
const { renderer, scene } = useTresContext()
onMounted(async () => {
const pmremGenerator = new PMREMGenerator(renderer.value);
pmremGenerator.compileEquirectangularShader();
const loadEnv = async () => {
const loader = new GainMapLoader(renderer.value)
const result = await loader.loadAsync([hdr_webp, hdr_gainmap, hdr_json,])
const result = await loader.loadAsync([
props.hdr_webp || hdr_webp,
props.hdr_gainmap || hdr_gainmap,
props.hdr_json || hdr_json,
])
const exrCubeRenderTarget = pmremGenerator.fromEquirectangular(result.renderTarget.texture);
const exrBackground = exrCubeRenderTarget.texture;
const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null;
scene.value.environment = newEnvMap
scene.value.background = exrBackground
scene.value.environment = result.renderTarget.texture
scene.value.background = result.renderTarget.texture
scene.value.background.mapping = EquirectangularReflectionMapping
// scene.value.backgroundBlurriness = 0.15
result.renderTarget.texture.dispose();
}
renderer.value.toneMapping = ReinhardToneMapping
onMounted(async () => {
loadEnv()
})
watch(() => props.hdr_webp, loadEnv)
</script>
<template></template>

View File

@ -1,46 +1,78 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import { Box3, Color, Group, Mesh, MeshStandardMaterial, PointLight, SphereGeometry, Vector3 } from 'three';
import { useTresContext, useSeek } from '@tresjs/core';
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import {
Box3, Color, DoubleSide, Group, Mesh, MeshBasicMaterial,
PlaneGeometry, SpriteMaterial, TextureLoader, Vector2, Vector3,
} from 'three';
// import { DepthOfField, EffectComposer, Pixelation } from '@tresjs/post-processing'
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { RGBShiftShader } from 'three/addons/shaders/RGBShiftShader.js';
import { DotScreenShader } from 'three/addons/shaders/DotScreenShader.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import '@tresjs/leches/styles'
import { useTresContext, useSeek, useRenderLoop, useLoop } from '@tresjs/core';
import { useGLTF } from '@tresjs/cientos'
import Env from './env.vue'
import { IMAGE_URL, SERVER_URL, } from '../../constants'
import { usePromoSidebar } from '../../stores/promo_sidebar';
import { usePromoScene } from '../../stores/promo_scene';
const props = defineProps(['source', 'loaded', 'loaded_pan'])
function shadows_and_pos(scene: any) {
scene.children.forEach((el: any) => {
el.receiveShadow = true
el.castShadow = true
// el.receiveShadow = true
// el.castShadow = true
shadows_and_pos(el)
})
}
const models = ref<model3DType[]>([])
const clickable = ref<clickableAreaType[]>([])
const clickable_objects = ref<any[]>([])
const clickable_items = ref<any[]>([])
const clickable_refs = ref<any[]>([])
const sidebar = usePromoSidebar();
const { controls, camera, scene } = useTresContext()
const { seekByName } = useSeek()
const sidebar_scene = usePromoScene()
const { renderer, controls, camera, scene, raycaster } = useTresContext()
const { seekByName, seekAllByName } = useSeek()
const envVars = reactive({}) as { hdr_gainmap?: string, hdr_json?: string, hdr_webp?: string }
// renderer.value.capabilities.maxTextures = 4
// renderer.value.capabilities.maxTextureSize = 512
// renderer.value.capabilities.precision = 'lowp'
let composer: any = null
const loadModels = async () => {
const res = await fetch(`${SERVER_URL}/api/obj/scene/${props.source}`)
const raw_data = await res.json() as scene3D
envVars.hdr_gainmap = raw_data.hdr_gainmap ? `${IMAGE_URL}/${raw_data.hdr_gainmap}` : undefined
envVars.hdr_json = raw_data.hdr_json ? `${IMAGE_URL}/${raw_data.hdr_json}` : undefined
envVars.hdr_webp = raw_data.hdr_webp ? `${IMAGE_URL}/${raw_data.hdr_webp}` : undefined
const data = raw_data.elements
if (!controls.value) return;
camera.value?.position.set(1, 1, 1);
controls.value.enabled = false;
(controls.value as any).minDistance = raw_data.min_distance;
(controls.value as any).maxDistance = raw_data.max_distance;
(controls.value as any)._needsUpdate = true;
(controls.value as any).update(1)
camera.value?.position.set(1, 1, 1);
camera.value?.lookAt(new Vector3(1, 1, 1));
(controls.value as any).update()
const sidebar_items = []
clickable_items.value = []
for (let index = 0; index < data.length; index++) {
const element = data[index];
sidebar_items.push({ ...element })
const item = {} as model3DType
item.modelUrl = `${IMAGE_URL}/${element.model_file}`
@ -55,47 +87,42 @@ const loadModels = async () => {
const clickable_areas = await res.json()
clickable.value.push(...clickable_areas)
}
sidebar_scene.setData(sidebar_items)
for (let index = 0; index < clickable.value.length; index++) {
const element = clickable.value[index];
const find_element = seekByName(scene.value, element.object_name)
if (!find_element) continue
const res_array = (find_element as Group).isGroup ? find_element?.children : [find_element]
// const res_array = (find_element as Group).isGroup ? find_element?.children : [find_element]
if (find_element && !(find_element as Group).isGroup) {
const world_position = new Vector3();
(find_element as Mesh).geometry.boundingBox.getCenter(world_position);
((find_element as Mesh).geometry.boundingBox as any).getCenter(world_position);
(find_element as Mesh).localToWorld(world_position);
const light = new PointLight()
light.position.set(world_position.x, world_position.y * 5, world_position.z)
light.color = index % 2 ? new Color('red') : new Color('green');
light.power = 10000;
const p = raw_data.min_distance * 0.05
const plane = new PlaneGeometry(p, p, 32)
const point = new Mesh(new SphereGeometry(2, 16, 16), new MeshStandardMaterial({
color: light.color,
emissive: light.color,
emissiveIntensity: 100
}))
point.position.set(light.position.x, light.position.y, light.position.z)
const mesh_material = new MeshBasicMaterial({ side: DoubleSide })
const sprite_material = new SpriteMaterial()
if (element.image) {
const map = new TextureLoader().load(`${IMAGE_URL}/${element.image}`);
mesh_material.map = map
sprite_material.map = map
} else {
mesh_material.color = new Color('red')
sprite_material.color = new Color('red')
}
const point = new Mesh(plane, mesh_material);
// const point = new Sprite(sprite_material)
point.position.set(world_position.x, p * 3, world_position.z)
// point.scale.set(p, p, 1)
point.name = `${element.id}_clickable`
// light.add(point)
point.renderOrder = 10
clickable_items.value.push(light)
if (clickable_items.value.find(el => el.name == point.name)) continue
clickable_items.value.push(point)
clickable_objects.value.push({
name: point.name,
target: element.id,
object: point,
})
}
for (let index = 0; index < res_array.length; index++) {
const r = res_array[index];
let res = {
name: r.name,
target: element.id,
object: r,
}
clickable_objects.value.push(res)
clickable_refs.value.push(ref(`${element.id}_clickable`))
}
}
@ -113,8 +140,36 @@ const loadModels = async () => {
controls.value.enabled = true;
props.loaded()
composer = new EffectComposer(renderer.value);
composer.addPass(new RenderPass(scene.value, camera.value));
const effect1 = new ShaderPass(DotScreenShader);
effect1.uniforms['scale'].value = 40;
composer.addPass(effect1);
function animate() {
requestAnimationFrame(animate);
composer.render();
}
// animate()
}
const { onLoop } = useRenderLoop()
onLoop(() => {
clickable_refs.value.map(el => {
// el.quaternion.copy(camera.value?.quaternion);
if (el.value[0] && typeof el.value[0].lookAt == 'function') {
el.value[0].lookAt(camera.value?.position)
}
})
})
const { onAfterRender } = useLoop()
onAfterRender(() => {
if (composer) {
composer.render()
}
})
const openSidebar = (id: number) => {
const target = clickable.value.find(el => el.id == id)
if (!target) return
@ -136,19 +191,55 @@ watch(() => props.source, () => {
if (loaded) {
loaded.children = []
}
sidebar.close()
loadModels()
})
onMounted(() => {
document.addEventListener('click', clickEvent)
if (sidebar.is_open) {
sidebar.close()
}
})
onUnmounted(() => { document.removeEventListener('click', clickEvent) })
const pointer = reactive({ x: 0, y: 0 })
const clickEvent = (event: MouseEvent) => {
const x = (event.clientX / window.innerWidth) * 2 - 1
const y = - (event.clientY / window.innerHeight) * 2 + 1
if (x == pointer.x && y == pointer.y) return
if (!camera.value) return
pointer.x = x
pointer.y = y
raycaster.value.setFromCamera(new Vector2(pointer.x, pointer.y), camera.value);
const intersects = raycaster.value.intersectObjects(seekAllByName(scene.value, '_clickable'));
const names = intersects.map(el => el.object.name ?? false).filter(Boolean)
if (names.length) {
openSidebar(parseInt(names[0].replace('_clickable', '')))
}
}
watch(() => sidebar_scene.list, () => {
sidebar_scene.list.forEach(element => {
const el = seekByName(scene.value, element.name)
if (!el) return
if (el.visible !== element.is_enabled) {
el.visible = element.is_enabled
}
});
}, { deep: true })
</script>
<template>
<TresGroup name="loaded">
<Env v-bind="envVars" />
<template v-for="item in models">
<TresGroup :name="item.name">
<TresObject3D v-bind="item.modelFile.clone()" />
</TresGroup>
</template>
<template v-for="item in clickable_items">
<TresObject3D v-if="item.type == 'PointLight'" v-bind="item.clone()" />
<TresMesh v-else @click="() => openSidebar(item.name.replace('_clickable', ''))" v-bind="item" />
<template v-for="(item, i) in clickable_items">
<TresMesh v-bind="item" :ref="clickable_refs[i]" />
</template>
</TresGroup>
</template>

View File

@ -1,13 +1,13 @@
<script setup lang="ts">
import { reactive, ref, watch } from 'vue';
import { reactive, ref, watch, computed } from 'vue';
import type { Ref } from 'vue'
import { useRoute } from 'vue-router';
import { RouterLink, useRoute } from 'vue-router';
import { Vector3 } from 'three';
import { TresCanvas } from '@tresjs/core';
import { StatsGl, OrbitControls } from '@tresjs/cientos'
import { OrbitControls } from '@tresjs/cientos'
import '@tresjs/leches/styles'
import Env from './env.vue'
import LoadModels from './load_models.vue'
import Sidebar from './sidebar.vue'
import { usePromoSidebar } from '../../stores/promo_sidebar';
@ -33,6 +33,7 @@ const camera = ref()
const cameraPosition = ref([1, 1, 1]) as unknown as Ref<Vector3>
const controlsState = reactive({
enableDamping: false,
maxPolarAngle: (Math.PI / 2) - 0.05,
minAzimuthAngle: (Math.PI / 2) - 0.02,
})
@ -56,23 +57,25 @@ watch(() => route.params.target, () => {
<template>
<div>
<div :class="[{ 'loading': !models_loading }, 'canvas-wrapper']">
<TresCanvas shadows window-size>
<Suspense>
<StatsGl />
</Suspense>
<TresCanvas window-size :alpha="false" power-preference="high-performance">
<TresPerspectiveCamera :position="cameraPosition" ref="camera" />
<OrbitControls v-bind="controlsState" @change="onChange" make-default />
<Suspense>
<Env />
</Suspense>
<Suspense>
<LoadModels :source="source" :loaded="set_model_load_status" :loaded_pan="loadedPan" />
</Suspense>
<TresMesh :position-y="0" :rotate-x="-Math.PI / 2" receive-shadow>
<TresMesh :position-y="-1" :rotate-x="-Math.PI / 2" receive-shadow name="ground" v-if="false">
<TresPlaneGeometry :args="[200, 200]" />
<TresShadowMaterial :opacity="0.2" />
</TresMesh>
</TresCanvas>
<div class="homelink">
<a href="#" @click.prevent="sidebar.open" v-if="!sidebar.is_open">
<i-mdi-page-previous-outline />
</a>
<RouterLink to="/promo/main/">
<i-mdi-home />
</RouterLink>
</div>
</div>
<Sidebar />
</div>
@ -93,4 +96,25 @@ watch(() => route.params.target, () => {
filter: blur(10px);
transition: all 300ms linear;
}
.homelink {
position: absolute;
right: 2rem;
bottom: 2rem;
svg {
font-size: 3rem;
padding: 1.5rem;
}
a {
margin-top: 2rem;
border-radius: 50%;
border: 1px solid white;
background: white;
line-height: 1;
font-size: 0;
display: block;
}
}
</style>

View File

@ -3,27 +3,44 @@ import { ref } from 'vue';
import { RouterLink } from 'vue-router';
import { onClickOutside } from '@vueuse/core'
import { usePromoSidebar } from '../../stores/promo_sidebar';
import { usePromoScene } from '../../stores/promo_scene';
const sidebar = usePromoSidebar()
const sidebar_scene = usePromoScene()
const sidebar_obj = ref()
// onClickOutside(sidebar_obj, () => sidebar.close())
</script>
<template>
<div class="sidebar" :class="[{ 'open': sidebar.is_open }]" ref="sidebar_obj">
<a href="#" @click.prevent="sidebar.close" class="sidebar-close">
<i-mdi-close />
</a>
<template v-if="sidebar.is_open">
<div class="sidebar-content">
<template v-if="!sidebar.is_open"></template>
<template v-else-if="sidebar.title">
<h2>{{ sidebar.title }}</h2>
<template v-if="sidebar.description">
<template v-for="p in sidebar.description.split('\n')">
<p>{{ p }}</p>
</template>
</template>
<RouterLink class="btn" :to="`/promo/main/${sidebar.target}`" v-if="sidebar.target">
{{ sidebar.target_name }}
</RouterLink>
</template>
<template v-else>
<span class="sidebar-list-item" v-for="item in sidebar_scene.list">
<input type="checkbox" v-model="item.is_enabled" :id="item.name" :disabled="item.can_not_disable" />
<label :for="item.name">
<h3>{{ item.name }}</h3>
<template v-for="p in item.description.split('\n')">
<p>{{ p }}</p>
</template>
</label>
</span>
</template>
</div>
</div>
</template>
<style scoped lang="scss">
@ -35,8 +52,9 @@ const sidebar_obj = ref()
right: -27vw;
bottom: 0;
transition: all 300ms linear;
line-height: 1.25;
padding: 2rem;
padding: 3rem 2rem 2rem;
&.open {
right: 0
@ -50,45 +68,68 @@ const sidebar_obj = ref()
color: black;
}
&-content {
max-height: 100%;
overflow: auto;
}
&-list-item {
display: flex;
label {
flex-grow: 1;
margin-left: 0.25rem
}
}
h2 {
text-align: center;
font-size: 2rem;
margin: 1rem
}
p,
h3 {
margin: 0.5rem 0;
}
p {
margin: 1rem 0;
font-size: 1.25rem;
}
h3 {
font-size: 1.5rem;
font-weight: bold
}
.btn {
color: white;
transition: .2s linear;
background: #0B63F6;
padding-top: 5px;
padding-bottom: 7px;
display: block;
margin: 0 auto;
background: #4FD1C5;
background: linear-gradient(90deg, rgba(129, 230, 217, 1) 0%, rgba(79, 209, 197, 1) 100%);
color: #313133;
transition: .2s linear;
padding: 1rem 1.5rem;
font-size: 1.25rem;
font-weight: 700;
text-align: center;
text-decoration: none;
min-width: 300px;
min-height: 60px;
display: inline-flex;
font-family: 'Nunito', sans-serif;
font-size: 22px;
align-items: center;
justify-content: center;
text-transform: uppercase;
letter-spacing: 1.3px;
font-weight: 700;
color: #313133;
background: #4FD1C5;
background: linear-gradient(90deg, rgba(129, 230, 217, 1) 0%, rgba(79, 209, 197, 1) 100%);
border-radius: 1000px;
box-shadow: 12px 12px 24px rgba(79, 209, 197, .64);
}
.btn:hover {
box-shadow: 0 0 0 2px white, 0 0 0 4px #3C82F8;
box-shadow: 0 0 0 2px white, inset 0 0 0 4px #3C82F8;
}
}
</style>

20
front/src/index.d.ts vendored
View File

@ -14,16 +14,21 @@ interface scene3D {
name: string
min_distance: number
max_distance: number
hdr_gainmap?: string
hdr_json?: string
hdr_webp?: string
elements: element3DType[]
}
interface element3DType {
id: number
model_file: string
name: string
description: string
description?: string
parent?: number,
min_distance?: number,
max_distance?: number,
is_enabled: boolean
can_not_disable: boolean
}
interface model3DType {
modelUrl?: string,
@ -35,13 +40,14 @@ interface clickableAreaType {
id: number;
name: string;
object_name: string;
image?: string;
source: number;
target: number;
target_name?: string
}
interface PromoSidebarData {
title: string
description: string
title?: string
description?: string
target?: string
target_name?: string
}
@ -49,3 +55,11 @@ interface PromoSidebar extends PromoSidebarData {
loading: boolean
is_open: boolean
}
interface PromoScene {
id: number
model_file: string
name: string
description: string
parent?: number
is_enabled: boolean
}

View File

@ -0,0 +1,12 @@
import { defineStore } from 'pinia'
export const usePromoScene = defineStore('promo_scene', {
state: () => {
return { list: [] as PromoScene[] }
},
actions: {
setData(data: PromoScene[]) {
this.list = data
}
}
})

View File

@ -3,10 +3,10 @@ import { defineStore } from 'pinia'
export const usePromoSidebar = defineStore('promo_sidebar', {
state: () => {
return {
title: 'Сайдбар',
description: 'Описание',
title: undefined,
description: undefined,
target: undefined,
target_name: 'Перейти дальше',
target_name: undefined,
loading: true,
is_open: false
} as PromoSidebar

6
package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "interactive-table",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}