forked from mns/mini-skamja
three js module
This commit is contained in:
parent
2faf0979fd
commit
e03889db97
|
@ -2,37 +2,28 @@
|
|||
//@ts-ignore
|
||||
import { useGLTF } from '@tresjs/cientos'
|
||||
import { useTresContext } from '@tresjs/core'
|
||||
import { Vector3 } from 'three'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { degToRad } from 'three/src/math/MathUtils.js'
|
||||
|
||||
const { camera, controls } = useTresContext()
|
||||
|
||||
const { scene: obj } = await useGLTF('/models/bench2_export-v1.glb')
|
||||
const scale = ref(1) // Масштаб объекта
|
||||
|
||||
onMounted(() => {
|
||||
if (obj && camera.value && controls.value) {
|
||||
const distance = 10
|
||||
const cameraOffset = { x: 1, y: 0.75, z: -1.25 } // Смещение камеры
|
||||
|
||||
// Вычисляем масштаб и позицию камеры
|
||||
const { scale: objectScale } = calculateScaleToFit(
|
||||
obj,
|
||||
camera.value as any,
|
||||
controls.value as any,
|
||||
distance,
|
||||
cameraOffset
|
||||
)
|
||||
|
||||
scale.value = objectScale
|
||||
const { scene } = await useGLTF('/models/table.glb')
|
||||
const object = calculateScaleToFit(
|
||||
scene,
|
||||
camera,
|
||||
controls,
|
||||
3,
|
||||
{
|
||||
x: Math.sin(degToRad(-30)), // Смещение по оси X
|
||||
y: 1, // Нет смещения по оси Y
|
||||
z: -Math.cos(degToRad(0)) // Смещение по оси Z
|
||||
}
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Suspense>
|
||||
<TresGroup :position-y="0" :scale="new Vector3(scale, scale, scale)">
|
||||
<ModelItem :model="obj" :target="[0, 0, 0]" />
|
||||
<TresGroup>
|
||||
<ModelItem :model="object" :target="[0, 0, 0]" />
|
||||
</TresGroup>
|
||||
</Suspense>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
@ -2,37 +2,28 @@
|
|||
//@ts-ignore
|
||||
import { useGLTF } from '@tresjs/cientos'
|
||||
import { useTresContext } from '@tresjs/core'
|
||||
import { Vector3 } from 'three'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { degToRad } from 'three/src/math/MathUtils.js'
|
||||
|
||||
const { camera, controls } = useTresContext()
|
||||
|
||||
const { scene: obj } = await useGLTF('/models/bench_export-v1.glb')
|
||||
const scale = ref(1) // Масштаб объекта
|
||||
|
||||
onMounted(() => {
|
||||
if (obj && camera.value && controls.value) {
|
||||
const distance = 10
|
||||
const cameraOffset = { x: -1, y: 0.75, z: -1.25 } // Смещение камеры
|
||||
|
||||
// Вычисляем масштаб и позицию камеры
|
||||
const { scale: objectScale } = calculateScaleToFit(
|
||||
obj,
|
||||
camera.value as any,
|
||||
controls.value as any,
|
||||
distance,
|
||||
cameraOffset
|
||||
)
|
||||
|
||||
scale.value = objectScale
|
||||
const { scene } = await useGLTF('/models/bench.glb')
|
||||
const object = calculateScaleToFit(
|
||||
scene,
|
||||
camera,
|
||||
controls,
|
||||
2,
|
||||
{
|
||||
x: Math.sin(degToRad(30)), // Смещение по оси X
|
||||
y: 1, // Нет смещения по оси Y
|
||||
z: -Math.cos(degToRad(0)) // Смещение по оси Z
|
||||
}
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Suspense>
|
||||
<TresGroup :position-y="0" :scale="new Vector3(scale, scale, scale)">
|
||||
<ModelItem :model="obj" :target="[0, 0, 0]" />
|
||||
<TresGroup>
|
||||
<ModelItem :model="object" :target="[0, 0, 0]" />
|
||||
</TresGroup>
|
||||
</Suspense>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
PCFSoftShadowMap,
|
||||
CineonToneMapping,
|
||||
PMREMGenerator,
|
||||
} from 'three';
|
||||
PCFSoftShadowMap,
|
||||
CineonToneMapping,
|
||||
PMREMGenerator,
|
||||
CanvasTexture
|
||||
} from 'three'
|
||||
|
||||
import { GainMapLoader, } from '@monogrid/gainmap-js'
|
||||
import { GainMapLoader } from '@monogrid/gainmap-js'
|
||||
|
||||
const { scene, renderer, camera } = useTresContext()
|
||||
|
||||
|
@ -15,26 +16,49 @@ renderer.value.toneMappingExposure = 1
|
|||
renderer.value.shadowMap.enabled = true
|
||||
renderer.value.shadowMap.type = PCFSoftShadowMap
|
||||
|
||||
const pmremGenerator = new PMREMGenerator(renderer.value);
|
||||
pmremGenerator.compileEquirectangularShader();
|
||||
const pmremGenerator = new PMREMGenerator(renderer.value)
|
||||
pmremGenerator.compileEquirectangularShader()
|
||||
|
||||
function createGradientTexture () {
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = 256
|
||||
canvas.height = 256
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return null
|
||||
|
||||
// Создаем линейный градиент
|
||||
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
|
||||
gradient.addColorStop(0, '#ff7eb3') // Верхний цвет
|
||||
gradient.addColorStop(1, '#4f46e5') // Нижний цвет
|
||||
|
||||
// Заполняем холст градиентом
|
||||
ctx.fillStyle = gradient
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// Создаем текстуру из холста
|
||||
return new CanvasTexture(canvas)
|
||||
}
|
||||
|
||||
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;
|
||||
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.25
|
||||
scene.value.environmentRotation.z = 0.25
|
||||
result.renderTarget.texture.dispose();
|
||||
scene.value.environment = newEnvMap
|
||||
scene.value.environmentIntensity = 1.25
|
||||
scene.value.environmentRotation.z = 0.25
|
||||
result.renderTarget.texture.dispose()
|
||||
})
|
||||
</script>
|
||||
<template></template>
|
||||
<template></template>
|
||||
|
|
|
@ -14,15 +14,26 @@ const props = defineProps({
|
|||
}
|
||||
})
|
||||
|
||||
const camera = ref()
|
||||
const controls = ref()
|
||||
|
||||
const controlsState = reactive({
|
||||
// minDistance: 2,
|
||||
// maxDistance: 10,
|
||||
enablePan: false,
|
||||
enableZoom: false,
|
||||
maxPolarAngle: Math.PI / 2 - 0.2
|
||||
minDistance: 2,
|
||||
maxDistance: 20,
|
||||
enablePan: false
|
||||
// enableZoom: false,
|
||||
// maxPolarAngle: Math.PI / 2 - 0.2
|
||||
})
|
||||
|
||||
const toggleModal = () => {}
|
||||
|
||||
const changeDistance = (v = 1) => {
|
||||
if (camera.value && controls.value) {
|
||||
const distance = camera.value.position.distanceTo(new Vector3(0, 0, 0))
|
||||
const r = distance + v
|
||||
camera.value.position.normalize().multiplyScalar(r)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -32,14 +43,45 @@ const toggleModal = () => {}
|
|||
<template #fallback>
|
||||
<div class="fallback">Загрузка 3D модели</div>
|
||||
</template>
|
||||
<TresCanvas height="600">
|
||||
<TresPerspectiveCamera />
|
||||
<OrbitControls v-bind="controlsState" make-default />
|
||||
<TresCanvas height="600" clear-color="#e2e8f0">
|
||||
<TresPerspectiveCamera
|
||||
:position="new Vector3(-7, 2, 4)"
|
||||
ref="camera"
|
||||
/>
|
||||
<OrbitControls v-bind="controlsState" ref="controls" make-default />
|
||||
<ModelEnv />
|
||||
<Suspense>
|
||||
<component :is="types[props.type]" />
|
||||
</Suspense>
|
||||
</TresCanvas>
|
||||
<div class="canvas-icons">
|
||||
<a
|
||||
href="#"
|
||||
@click.prevent="changeDistance(-0.5)"
|
||||
:class="[
|
||||
{
|
||||
disabled: camera
|
||||
? camera.position.distanceTo(new Vector3(0, 0, 0)) <= controlsState.minDistance
|
||||
: null
|
||||
}
|
||||
]"
|
||||
>
|
||||
<Icon name="mdi:plus-circle-outline" />
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
@click.prevent="changeDistance(0.5)"
|
||||
:class="[
|
||||
{
|
||||
disabled: camera
|
||||
? camera.position.distanceTo(new Vector3(0, 0, 0)) >= controlsState.maxDistance
|
||||
: null
|
||||
}
|
||||
]"
|
||||
>
|
||||
<Icon name="mdi:minus-circle-outline" />
|
||||
</a>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
<button @click.prevent="toggleModal">Рассчитать</button>
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,38 +1,54 @@
|
|||
import { Box3, PerspectiveCamera, Vector3 } from 'three'
|
||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||
import { Box3, PerspectiveCamera, Vector3, Mesh, Camera } from 'three'
|
||||
import { degToRad } from 'three/src/math/MathUtils.js'
|
||||
|
||||
/**
|
||||
* Вычисляет масштаб объекта и позицию камеры, чтобы объект полностью помещался в видимой области.
|
||||
* @param object - Объект Three.js (например, загруженная модель).
|
||||
* @param camera - Экземпляр камеры.
|
||||
* @param controls - Экземпляр OrbitControls.
|
||||
* @param distance - Расстояние от камеры до объекта.
|
||||
* @param cameraOffset - Коэффициенты смещения камеры (x, y, z).
|
||||
*/
|
||||
export default function (
|
||||
export default function calculateScaleToFit(
|
||||
object: any,
|
||||
camera: PerspectiveCamera,
|
||||
camera: ComputedRef<Camera | undefined>,
|
||||
controls: any,
|
||||
distance: number,
|
||||
cameraOffset: { x: number; y: number; z: number }
|
||||
) {
|
||||
// Создаем новый BoundingBox
|
||||
const bbox = new Box3()
|
||||
|
||||
// Обновляем мировую матрицу объекта
|
||||
object.updateWorldMatrix(true, true)
|
||||
|
||||
const fov = camera.fov // Угол обзора камеры
|
||||
const aspect = camera.aspect // Соотношение сторон
|
||||
// Перебираем все дочерние элементы объекта
|
||||
object.traverse((child: any) => {
|
||||
if (child.isMesh) {
|
||||
// Добавляем геометрию каждого меша в bounding box
|
||||
const geometry = child.geometry
|
||||
if (geometry) {
|
||||
geometry.computeBoundingBox() // Убедимся, что bounding box геометрии вычислен
|
||||
const meshBBox = geometry.boundingBox.clone()
|
||||
if (meshBBox) {
|
||||
// Применяем мировую матрицу к bounding box
|
||||
meshBBox.applyMatrix4(child.matrixWorld)
|
||||
bbox.union(meshBBox) // Объединяем с общим bounding box
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const bbox = new Box3().setFromObject(object) // Вычисляем bounding box
|
||||
// Вычисляем центр и размер bounding box
|
||||
const center = new Vector3()
|
||||
bbox.getCenter(center) // Центр объекта
|
||||
bbox.getCenter(center)
|
||||
|
||||
const size = new Vector3()
|
||||
bbox.getSize(size) // Размеры объекта
|
||||
bbox.getSize(size)
|
||||
|
||||
// Рассчитываем масштаб, чтобы объект полностью помещался в видимой области
|
||||
const vFOV = degToRad(fov) // Угол обзора в радианах
|
||||
const visibleHeight = 2 * Math.tan(vFOV / 2) * distance // Видимая высота
|
||||
const visibleWidth = visibleHeight * aspect // Видимая ширина
|
||||
// Центрируем объект
|
||||
object.position.sub(center)
|
||||
object.updateWorldMatrix(true, true)
|
||||
|
||||
// Рассчитываем масштаб
|
||||
const fov = (camera.value as PerspectiveCamera).fov
|
||||
const aspect = (camera.value as PerspectiveCamera).aspect
|
||||
|
||||
const vFOV = degToRad(fov)
|
||||
const visibleHeight = 2 * Math.tan(vFOV / 2) * distance
|
||||
const visibleWidth = visibleHeight * aspect
|
||||
|
||||
const scaleX = visibleWidth / size.x
|
||||
const scaleY = visibleHeight / size.y
|
||||
|
@ -40,18 +56,20 @@ export default function (
|
|||
|
||||
const scale = Math.min(scaleX, scaleY, scaleZ)
|
||||
|
||||
// Позиционируем камеру с учетом смещения
|
||||
camera.position.set(
|
||||
center.x + distance * cameraOffset.x,
|
||||
center.y + distance * cameraOffset.y,
|
||||
center.z + distance * cameraOffset.z
|
||||
)
|
||||
// Масштабируем объект
|
||||
object.scale.setScalar(scale)
|
||||
object.updateWorldMatrix(true, true)
|
||||
|
||||
// Устанавливаем цель для OrbitControls
|
||||
if (controls) {
|
||||
controls.target.set(center.x, center.y, center.z)
|
||||
controls.update() // Обновляем контроллы
|
||||
if (camera.value) {
|
||||
const [x, y, z] = Object.values(cameraOffset).map(el => el * distance);
|
||||
camera.value.position.set(x, y, z);
|
||||
}
|
||||
|
||||
return { scale, center }
|
||||
// Настройка OrbitControls
|
||||
if (controls.value) {
|
||||
controls.value.target.set(...Object.values(center.multiplyScalar(1)))
|
||||
controls.value.update()
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
Loading…
Reference in New Issue