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 django.contrib import admin
from .models import ClickableArea, Element3D, Scene3D 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(Element3D)
admin.site.register(ClickableArea) admin.site.register(ClickableArea)

View File

@ -1,5 +1,7 @@
from PIL import Image
from django.db import models from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.exceptions import ValidationError
import logging import logging
logger = logging.getLogger("root") logger = logging.getLogger("root")
@ -16,13 +18,16 @@ class Element3D(models.Model):
parent = models.ForeignKey("self", on_delete=models.PROTECT, blank=True, null=True) parent = models.ForeignKey("self", on_delete=models.PROTECT, blank=True, null=True)
model_file = models.FileField(upload_to=group_based_upload_to) model_file = models.FileField(upload_to=group_based_upload_to)
name = models.CharField(max_length=255) 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): def __str__(self):
return self.name return self.name
class Scene3D(models.Model): class Scene3D(models.Model):
filter_horizontal = ("elements",)
name = models.CharField( name = models.CharField(
max_length=120, max_length=120,
) )
@ -32,13 +37,30 @@ class Scene3D(models.Model):
validators=[MinValueValidator(1), MaxValueValidator(600)], blank=True, null=True validators=[MinValueValidator(1), MaxValueValidator(600)], blank=True, null=True
) )
max_distance = models.IntegerField( 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): def __str__(self):
return self.name 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): class ClickableArea(models.Model):
name = models.CharField("название", max_length=255) name = models.CharField("название", max_length=255)
description = models.TextField("описание") description = models.TextField("описание")
@ -59,6 +81,15 @@ class ClickableArea(models.Model):
Element3D, Element3D,
on_delete=models.PROTECT, on_delete=models.PROTECT,
) )
image = models.ImageField(
"Картинка",
upload_to=group_based_upload_to,
validators=[
maximum_size_validator,
],
blank=True,
null=True,
)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -12,6 +12,9 @@ class Element3DSerializer(serializers.ModelSerializer):
class Scene3DSerializer(serializers.ModelSerializer): class Scene3DSerializer(serializers.ModelSerializer):
elements = Element3DSerializer(many=True) 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: class Meta:
model = Scene3D model = Scene3D
@ -20,6 +23,8 @@ class Scene3DSerializer(serializers.ModelSerializer):
class ClickableAreaSerializer(serializers.ModelSerializer): class ClickableAreaSerializer(serializers.ModelSerializer):
image = serializers.ImageField(use_url=False)
class Meta: class Meta:
model = ClickableArea model = ClickableArea
fields = "__all__" fields = "__all__"

View File

@ -1,3 +1,3 @@
# VITE_SERVER_URL='http://localhost:8000'
VITE_SERVER_URL='https://demo.kustarshina.ru' VITE_SERVER_URL='https://demo.kustarshina.ru'
VITE_IMAGE_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'] IMdiHexagonOutline: typeof import('~icons/mdi/hexagon-outline')['default']
IMdiHome: typeof import('~icons/mdi/home')['default'] IMdiHome: typeof import('~icons/mdi/home')['default']
IMdiMonitorScreenshot: typeof import('~icons/mdi/monitor-screenshot')['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'] IMdiShop: typeof import('~icons/mdi/shop')['default']
IMdiVideo3d: typeof import('~icons/mdi/video3d')['default'] IMdiVideo3d: typeof import('~icons/mdi/video3d')['default']
Item: typeof import('./src/components/Floorplan/item.vue')['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", "@fireworks-js/vue": "^2.10.7",
"@monogrid/gainmap-js": "^3.0.5", "@monogrid/gainmap-js": "^3.0.5",
"@tresjs/cientos": "^3.9.0", "@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/components": "^10.9.0",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"d3": "^7.9.0", "d3": "^7.9.0",
@ -84,7 +86,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"aix" "aix"
@ -100,7 +101,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -116,7 +116,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -132,7 +131,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -148,7 +146,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -164,7 +161,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -180,7 +176,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@ -196,7 +191,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@ -212,7 +206,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -228,7 +221,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -244,7 +236,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -260,7 +251,6 @@
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -276,7 +266,6 @@
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -292,7 +281,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -308,7 +296,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -324,7 +311,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -340,7 +326,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -356,7 +341,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"netbsd" "netbsd"
@ -372,7 +356,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"openbsd" "openbsd"
@ -388,7 +371,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"sunos" "sunos"
@ -404,7 +386,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -420,7 +401,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -436,7 +416,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -594,7 +573,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -607,7 +585,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -620,7 +597,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -633,7 +609,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -646,7 +621,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -659,7 +633,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -672,7 +645,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -685,7 +657,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -698,7 +669,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -711,7 +681,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -724,7 +693,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -737,7 +705,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -750,7 +717,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -763,7 +729,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -776,7 +741,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -789,7 +753,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -815,7 +778,7 @@
"vue": ">=3.3" "vue": ">=3.3"
} }
}, },
"node_modules/@tresjs/core": { "node_modules/@tresjs/cientos/node_modules/@tresjs/core": {
"version": "3.9.0", "version": "3.9.0",
"resolved": "https://registry.npmjs.org/@tresjs/core/-/core-3.9.0.tgz", "resolved": "https://registry.npmjs.org/@tresjs/core/-/core-3.9.0.tgz",
"integrity": "sha512-6el70oXSduTvSA3XCI8/rQV2GzcgCLceZKA443CTU/MBPmRDULJ12q/UYl18Ij4CJ68rTqgVi0Da+WNMrs784A==", "integrity": "sha512-6el70oXSduTvSA3XCI8/rQV2GzcgCLceZKA443CTU/MBPmRDULJ12q/UYl18Ij4CJ68rTqgVi0Da+WNMrs784A==",
@ -829,6 +792,20 @@
"vue": ">=3.3" "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": { "node_modules/@tresjs/core/node_modules/@vueuse/core": {
"version": "10.10.0", "version": "10.10.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.10.0.tgz", "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": { "node_modules/@trysound/sax": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@ -1186,8 +1214,7 @@
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
"dev": true
}, },
"node_modules/@types/geojson": { "node_modules/@types/geojson": {
"version": "7946.0.14", "version": "7946.0.14",
@ -1234,6 +1261,14 @@
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.15.tgz", "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.15.tgz",
"integrity": "sha512-nC9116Gd4N+CqTxqo6gvCfhAMAzgRcfS8ZsciNodHq8uwW4JCVKwhagw8yN0XmC7mHrLnWqniJpoVEiR+72Drw==" "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": { "node_modules/@vitejs/plugin-vue": {
"version": "5.0.4", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
@ -1527,7 +1562,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
@ -1546,7 +1581,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true, "devOptional": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
}, },
@ -1572,7 +1607,7 @@
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"fill-range": "^7.1.1" "fill-range": "^7.1.1"
}, },
@ -1598,7 +1633,7 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"anymatch": "~3.1.2", "anymatch": "~3.1.2",
"braces": "~3.0.2", "braces": "~3.0.2",
@ -2202,7 +2237,6 @@
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"bin": { "bin": {
"esbuild": "bin/esbuild" "esbuild": "bin/esbuild"
@ -2298,7 +2332,7 @@
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -2345,7 +2379,6 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
@ -2380,7 +2413,7 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
}, },
@ -2467,7 +2500,7 @@
"version": "4.3.5", "version": "4.3.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
"integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
"dev": true "devOptional": true
}, },
"node_modules/inherits": { "node_modules/inherits": {
"version": "2.0.4", "version": "2.0.4",
@ -2486,7 +2519,7 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"binary-extensions": "^2.0.0" "binary-extensions": "^2.0.0"
}, },
@ -2510,7 +2543,7 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true, "devOptional": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -2519,7 +2552,7 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
}, },
@ -2531,7 +2564,7 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true, "devOptional": true,
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
} }
@ -2748,7 +2781,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true, "devOptional": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -2882,7 +2915,7 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true, "devOptional": true,
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
}, },
@ -2978,6 +3011,17 @@
"node": "^10 || ^12 || >=14" "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": { "node_modules/potpack": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
@ -3027,7 +3071,7 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
}, },
@ -3076,7 +3120,6 @@
"version": "4.16.4", "version": "4.16.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.4.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.4.tgz",
"integrity": "sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==", "integrity": "sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==",
"dev": true,
"dependencies": { "dependencies": {
"@types/estree": "1.0.5" "@types/estree": "1.0.5"
}, },
@ -3144,7 +3187,7 @@
"version": "1.75.0", "version": "1.75.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz",
"integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0", "immutable": "^4.0.0",
@ -3354,7 +3397,7 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },
@ -3518,7 +3561,6 @@
"version": "5.2.10", "version": "5.2.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz",
"integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==", "integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==",
"dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.20.1", "esbuild": "^0.20.1",
"postcss": "^8.4.38", "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": { "node_modules/vite-plugin-static-copy": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-1.0.5.tgz", "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", "@fireworks-js/vue": "^2.10.7",
"@monogrid/gainmap-js": "^3.0.5", "@monogrid/gainmap-js": "^3.0.5",
"@tresjs/cientos": "^3.9.0", "@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/components": "^10.9.0",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"d3": "^7.9.0", "d3": "^7.9.0",

View File

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

View File

@ -1,46 +1,78 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue'; import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import { Box3, Color, Group, Mesh, MeshStandardMaterial, PointLight, SphereGeometry, Vector3 } from 'three'; import {
import { useTresContext, useSeek } from '@tresjs/core'; 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 { useGLTF } from '@tresjs/cientos'
import Env from './env.vue'
import { IMAGE_URL, SERVER_URL, } from '../../constants' import { IMAGE_URL, SERVER_URL, } from '../../constants'
import { usePromoSidebar } from '../../stores/promo_sidebar'; import { usePromoSidebar } from '../../stores/promo_sidebar';
import { usePromoScene } from '../../stores/promo_scene';
const props = defineProps(['source', 'loaded', 'loaded_pan']) const props = defineProps(['source', 'loaded', 'loaded_pan'])
function shadows_and_pos(scene: any) { function shadows_and_pos(scene: any) {
scene.children.forEach((el: any) => { scene.children.forEach((el: any) => {
el.receiveShadow = true // el.receiveShadow = true
el.castShadow = true // el.castShadow = true
shadows_and_pos(el) shadows_and_pos(el)
}) })
} }
const models = ref<model3DType[]>([]) const models = ref<model3DType[]>([])
const clickable = ref<clickableAreaType[]>([]) const clickable = ref<clickableAreaType[]>([])
const clickable_objects = ref<any[]>([])
const clickable_items = ref<any[]>([]) const clickable_items = ref<any[]>([])
const clickable_refs = ref<any[]>([])
const sidebar = usePromoSidebar(); const sidebar = usePromoSidebar();
const { controls, camera, scene } = useTresContext() const sidebar_scene = usePromoScene()
const { seekByName } = useSeek() 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 loadModels = async () => {
const res = await fetch(`${SERVER_URL}/api/obj/scene/${props.source}`) const res = await fetch(`${SERVER_URL}/api/obj/scene/${props.source}`)
const raw_data = await res.json() as scene3D 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 const data = raw_data.elements
if (!controls.value) return; if (!controls.value) return;
camera.value?.position.set(1, 1, 1);
controls.value.enabled = false; controls.value.enabled = false;
(controls.value as any).minDistance = raw_data.min_distance; (controls.value as any).minDistance = raw_data.min_distance;
(controls.value as any).maxDistance = raw_data.max_distance; (controls.value as any).maxDistance = raw_data.max_distance;
(controls.value as any)._needsUpdate = true; (controls.value as any)._needsUpdate = true;
(controls.value as any).update(1) (controls.value as any).update()
camera.value?.position.set(1, 1, 1);
camera.value?.lookAt(new Vector3(1, 1, 1));
const sidebar_items = []
clickable_items.value = []
for (let index = 0; index < data.length; index++) { for (let index = 0; index < data.length; index++) {
const element = data[index]; const element = data[index];
sidebar_items.push({ ...element })
const item = {} as model3DType const item = {} as model3DType
item.modelUrl = `${IMAGE_URL}/${element.model_file}` item.modelUrl = `${IMAGE_URL}/${element.model_file}`
@ -55,47 +87,42 @@ const loadModels = async () => {
const clickable_areas = await res.json() const clickable_areas = await res.json()
clickable.value.push(...clickable_areas) clickable.value.push(...clickable_areas)
} }
sidebar_scene.setData(sidebar_items)
for (let index = 0; index < clickable.value.length; index++) { for (let index = 0; index < clickable.value.length; index++) {
const element = clickable.value[index]; const element = clickable.value[index];
const find_element = seekByName(scene.value, element.object_name) const find_element = seekByName(scene.value, element.object_name)
if (!find_element) continue 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) { if (find_element && !(find_element as Group).isGroup) {
const world_position = new Vector3(); 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); (find_element as Mesh).localToWorld(world_position);
const light = new PointLight() const p = raw_data.min_distance * 0.05
light.position.set(world_position.x, world_position.y * 5, world_position.z) const plane = new PlaneGeometry(p, p, 32)
light.color = index % 2 ? new Color('red') : new Color('green');
light.power = 10000;
const point = new Mesh(new SphereGeometry(2, 16, 16), new MeshStandardMaterial({ const mesh_material = new MeshBasicMaterial({ side: DoubleSide })
color: light.color, const sprite_material = new SpriteMaterial()
emissive: light.color, if (element.image) {
emissiveIntensity: 100 const map = new TextureLoader().load(`${IMAGE_URL}/${element.image}`);
})) mesh_material.map = map
point.position.set(light.position.x, light.position.y, light.position.z) sprite_material.map = map
point.name = `${element.id}_clickable` } else {
// light.add(point) mesh_material.color = new Color('red')
sprite_material.color = new Color('red')
clickable_items.value.push(light)
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)
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`
point.renderOrder = 10
if (clickable_items.value.find(el => el.name == point.name)) continue
clickable_items.value.push(point)
clickable_refs.value.push(ref(`${element.id}_clickable`))
} }
} }
@ -113,8 +140,36 @@ const loadModels = async () => {
controls.value.enabled = true; controls.value.enabled = true;
props.loaded() 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 openSidebar = (id: number) => {
const target = clickable.value.find(el => el.id == id) const target = clickable.value.find(el => el.id == id)
if (!target) return if (!target) return
@ -136,19 +191,55 @@ watch(() => props.source, () => {
if (loaded) { if (loaded) {
loaded.children = [] loaded.children = []
} }
sidebar.close()
loadModels() 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> </script>
<template> <template>
<TresGroup name="loaded"> <TresGroup name="loaded">
<Env v-bind="envVars" />
<template v-for="item in models"> <template v-for="item in models">
<TresGroup :name="item.name"> <TresGroup :name="item.name">
<TresObject3D v-bind="item.modelFile.clone()" /> <TresObject3D v-bind="item.modelFile.clone()" />
</TresGroup> </TresGroup>
</template> </template>
<template v-for="item in clickable_items"> <template v-for="(item, i) in clickable_items">
<TresObject3D v-if="item.type == 'PointLight'" v-bind="item.clone()" /> <TresMesh v-bind="item" :ref="clickable_refs[i]" />
<TresMesh v-else @click="() => openSidebar(item.name.replace('_clickable', ''))" v-bind="item" />
</template> </template>
</TresGroup> </TresGroup>
</template> </template>

View File

@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch } from 'vue'; import { reactive, ref, watch, computed } from 'vue';
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { useRoute } from 'vue-router'; import { RouterLink, useRoute } from 'vue-router';
import { Vector3 } from 'three'; import { Vector3 } from 'three';
import { TresCanvas } from '@tresjs/core'; 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 LoadModels from './load_models.vue'
import Sidebar from './sidebar.vue' import Sidebar from './sidebar.vue'
import { usePromoSidebar } from '../../stores/promo_sidebar'; 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 cameraPosition = ref([1, 1, 1]) as unknown as Ref<Vector3>
const controlsState = reactive({ const controlsState = reactive({
enableDamping: false,
maxPolarAngle: (Math.PI / 2) - 0.05, maxPolarAngle: (Math.PI / 2) - 0.05,
minAzimuthAngle: (Math.PI / 2) - 0.02, minAzimuthAngle: (Math.PI / 2) - 0.02,
}) })
@ -56,23 +57,25 @@ watch(() => route.params.target, () => {
<template> <template>
<div> <div>
<div :class="[{ 'loading': !models_loading }, 'canvas-wrapper']"> <div :class="[{ 'loading': !models_loading }, 'canvas-wrapper']">
<TresCanvas shadows window-size> <TresCanvas window-size :alpha="false" power-preference="high-performance">
<Suspense>
<StatsGl />
</Suspense>
<TresPerspectiveCamera :position="cameraPosition" ref="camera" /> <TresPerspectiveCamera :position="cameraPosition" ref="camera" />
<OrbitControls v-bind="controlsState" @change="onChange" make-default /> <OrbitControls v-bind="controlsState" @change="onChange" make-default />
<Suspense>
<Env />
</Suspense>
<Suspense> <Suspense>
<LoadModels :source="source" :loaded="set_model_load_status" :loaded_pan="loadedPan" /> <LoadModels :source="source" :loaded="set_model_load_status" :loaded_pan="loadedPan" />
</Suspense> </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]" /> <TresPlaneGeometry :args="[200, 200]" />
<TresShadowMaterial :opacity="0.2" /> <TresShadowMaterial :opacity="0.2" />
</TresMesh> </TresMesh>
</TresCanvas> </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> </div>
<Sidebar /> <Sidebar />
</div> </div>
@ -93,4 +96,25 @@ watch(() => route.params.target, () => {
filter: blur(10px); filter: blur(10px);
transition: all 300ms linear; 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> </style>

View File

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

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

@ -14,16 +14,21 @@ interface scene3D {
name: string name: string
min_distance: number min_distance: number
max_distance: number max_distance: number
hdr_gainmap?: string
hdr_json?: string
hdr_webp?: string
elements: element3DType[] elements: element3DType[]
} }
interface element3DType { interface element3DType {
id: number id: number
model_file: string model_file: string
name: string name: string
description: string description?: string
parent?: number, parent?: number,
min_distance?: number, min_distance?: number,
max_distance?: number, max_distance?: number,
is_enabled: boolean
can_not_disable: boolean
} }
interface model3DType { interface model3DType {
modelUrl?: string, modelUrl?: string,
@ -35,13 +40,14 @@ interface clickableAreaType {
id: number; id: number;
name: string; name: string;
object_name: string; object_name: string;
image?: string;
source: number; source: number;
target: number; target: number;
target_name?: string target_name?: string
} }
interface PromoSidebarData { interface PromoSidebarData {
title: string title?: string
description: string description?: string
target?: string target?: string
target_name?: string target_name?: string
} }
@ -49,3 +55,11 @@ interface PromoSidebar extends PromoSidebarData {
loading: boolean loading: boolean
is_open: 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', { export const usePromoSidebar = defineStore('promo_sidebar', {
state: () => { state: () => {
return { return {
title: 'Сайдбар', title: undefined,
description: 'Описание', description: undefined,
target: undefined, target: undefined,
target_name: 'Перейти дальше', target_name: undefined,
loading: true, loading: true,
is_open: false is_open: false
} as PromoSidebar } as PromoSidebar

6
package-lock.json generated Normal file
View File

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