mns
/
urna
forked from mns/mini-skamja
4
0
Fork 0

three js module

This commit is contained in:
Kseninia Mikhaylova 2025-03-14 11:37:31 +03:00
parent 2faf0979fd
commit e03889db97
8 changed files with 177 additions and 111 deletions

View File

@ -2,37 +2,28 @@
//@ts-ignore //@ts-ignore
import { useGLTF } from '@tresjs/cientos' import { useGLTF } from '@tresjs/cientos'
import { useTresContext } from '@tresjs/core' import { useTresContext } from '@tresjs/core'
import { Vector3 } from 'three' import { degToRad } from 'three/src/math/MathUtils.js'
import { ref, onMounted } from 'vue'
const { camera, controls } = useTresContext() const { camera, controls } = useTresContext()
const { scene: obj } = await useGLTF('/models/bench2_export-v1.glb') const { scene } = await useGLTF('/models/table.glb')
const scale = ref(1) // Масштаб объекта const object = calculateScaleToFit(
scene,
onMounted(() => { camera,
if (obj && camera.value && controls.value) { controls,
const distance = 10 3,
const cameraOffset = { x: 1, y: 0.75, z: -1.25 } // Смещение камеры {
x: Math.sin(degToRad(-30)), // Смещение по оси X
// Вычисляем масштаб и позицию камеры y: 1, // Нет смещения по оси Y
const { scale: objectScale } = calculateScaleToFit( z: -Math.cos(degToRad(0)) // Смещение по оси Z
obj,
camera.value as any,
controls.value as any,
distance,
cameraOffset
)
scale.value = objectScale
} }
}) )
</script> </script>
<template> <template>
<Suspense> <Suspense>
<TresGroup :position-y="0" :scale="new Vector3(scale, scale, scale)"> <TresGroup>
<ModelItem :model="obj" :target="[0, 0, 0]" /> <ModelItem :model="object" :target="[0, 0, 0]" />
</TresGroup> </TresGroup>
</Suspense> </Suspense>
</template> </template>

View File

@ -2,37 +2,28 @@
//@ts-ignore //@ts-ignore
import { useGLTF } from '@tresjs/cientos' import { useGLTF } from '@tresjs/cientos'
import { useTresContext } from '@tresjs/core' import { useTresContext } from '@tresjs/core'
import { Vector3 } from 'three' import { degToRad } from 'three/src/math/MathUtils.js'
import { ref, onMounted } from 'vue'
const { camera, controls } = useTresContext() const { camera, controls } = useTresContext()
const { scene: obj } = await useGLTF('/models/bench_export-v1.glb') const { scene } = await useGLTF('/models/bench.glb')
const scale = ref(1) // Масштаб объекта const object = calculateScaleToFit(
scene,
onMounted(() => { camera,
if (obj && camera.value && controls.value) { controls,
const distance = 10 2,
const cameraOffset = { x: -1, y: 0.75, z: -1.25 } // Смещение камеры {
x: Math.sin(degToRad(30)), // Смещение по оси X
// Вычисляем масштаб и позицию камеры y: 1, // Нет смещения по оси Y
const { scale: objectScale } = calculateScaleToFit( z: -Math.cos(degToRad(0)) // Смещение по оси Z
obj,
camera.value as any,
controls.value as any,
distance,
cameraOffset
)
scale.value = objectScale
} }
}) )
</script> </script>
<template> <template>
<Suspense> <Suspense>
<TresGroup :position-y="0" :scale="new Vector3(scale, scale, scale)"> <TresGroup>
<ModelItem :model="obj" :target="[0, 0, 0]" /> <ModelItem :model="object" :target="[0, 0, 0]" />
</TresGroup> </TresGroup>
</Suspense> </Suspense>
</template> </template>

View File

@ -3,9 +3,10 @@ import {
PCFSoftShadowMap, PCFSoftShadowMap,
CineonToneMapping, CineonToneMapping,
PMREMGenerator, PMREMGenerator,
} from 'three'; CanvasTexture
} from 'three'
import { GainMapLoader, } from '@monogrid/gainmap-js' import { GainMapLoader } from '@monogrid/gainmap-js'
const { scene, renderer, camera } = useTresContext() const { scene, renderer, camera } = useTresContext()
@ -15,26 +16,49 @@ renderer.value.toneMappingExposure = 1
renderer.value.shadowMap.enabled = true renderer.value.shadowMap.enabled = true
renderer.value.shadowMap.type = PCFSoftShadowMap renderer.value.shadowMap.type = PCFSoftShadowMap
const pmremGenerator = new PMREMGenerator(renderer.value); const pmremGenerator = new PMREMGenerator(renderer.value)
pmremGenerator.compileEquirectangularShader(); 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 () => { onMounted(async () => {
const loader = new GainMapLoader(renderer.value) const loader = new GainMapLoader(renderer.value)
const result = await loader.loadAsync([ const result = await loader.loadAsync([
'hdrmaps/hdr.webp', 'hdrmaps/hdr.webp',
'hdrmaps/hdr-gainmap.webp', 'hdrmaps/hdr-gainmap.webp',
'hdrmaps/hdr.json', 'hdrmaps/hdr.json'
]) ])
if (renderer.value && camera.value) { if (renderer.value && camera.value) {
renderer.value.render(scene.value, camera.value) renderer.value.render(scene.value, camera.value)
} }
const exrCubeRenderTarget = pmremGenerator.fromEquirectangular(result.renderTarget.texture); const exrCubeRenderTarget = pmremGenerator.fromEquirectangular(
const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null; result.renderTarget.texture
)
const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null
scene.value.environment = newEnvMap scene.value.environment = newEnvMap
scene.value.environmentIntensity = 1.25 scene.value.environmentIntensity = 1.25
scene.value.environmentRotation.z = 0.25 scene.value.environmentRotation.z = 0.25
result.renderTarget.texture.dispose(); result.renderTarget.texture.dispose()
}) })
</script> </script>
<template></template> <template></template>

View File

@ -14,15 +14,26 @@ const props = defineProps({
} }
}) })
const camera = ref()
const controls = ref()
const controlsState = reactive({ const controlsState = reactive({
// minDistance: 2, minDistance: 2,
// maxDistance: 10, maxDistance: 20,
enablePan: false, enablePan: false
enableZoom: false, // enableZoom: false,
maxPolarAngle: Math.PI / 2 - 0.2 // maxPolarAngle: Math.PI / 2 - 0.2
}) })
const toggleModal = () => {} 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> </script>
<template> <template>
@ -32,14 +43,45 @@ const toggleModal = () => {}
<template #fallback> <template #fallback>
<div class="fallback">Загрузка 3D модели</div> <div class="fallback">Загрузка 3D модели</div>
</template> </template>
<TresCanvas height="600"> <TresCanvas height="600" clear-color="#e2e8f0">
<TresPerspectiveCamera /> <TresPerspectiveCamera
<OrbitControls v-bind="controlsState" make-default /> :position="new Vector3(-7, 2, 4)"
ref="camera"
/>
<OrbitControls v-bind="controlsState" ref="controls" make-default />
<ModelEnv /> <ModelEnv />
<Suspense> <Suspense>
<component :is="types[props.type]" /> <component :is="types[props.type]" />
</Suspense> </Suspense>
</TresCanvas> </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> </ClientOnly>
</div> </div>
<button @click.prevent="toggleModal">Рассчитать</button> <button @click.prevent="toggleModal">Рассчитать</button>

BIN
public/models/bench.glb Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,38 +1,54 @@
import { Box3, PerspectiveCamera, Vector3 } from 'three' import { Box3, PerspectiveCamera, Vector3, Mesh, Camera } from 'three'
import { degToRad } from 'three/src/math/MathUtils.js'; import { degToRad } from 'three/src/math/MathUtils.js'
/** export default function calculateScaleToFit(
* Вычисляет масштаб объекта и позицию камеры, чтобы объект полностью помещался в видимой области.
* @param object - Объект Three.js (например, загруженная модель).
* @param camera - Экземпляр камеры.
* @param controls - Экземпляр OrbitControls.
* @param distance - Расстояние от камеры до объекта.
* @param cameraOffset - Коэффициенты смещения камеры (x, y, z).
*/
export default function (
object: any, object: any,
camera: PerspectiveCamera, camera: ComputedRef<Camera | undefined>,
controls: any, controls: any,
distance: number, distance: number,
cameraOffset: { x: number; y: number; z: number } cameraOffset: { x: number; y: number; z: number }
) { ) {
// Создаем новый BoundingBox
const bbox = new Box3()
// Обновляем мировую матрицу объекта // Обновляем мировую матрицу объекта
object.updateWorldMatrix(true, true) 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() const center = new Vector3()
bbox.getCenter(center) // Центр объекта bbox.getCenter(center)
const size = new Vector3() const size = new Vector3()
bbox.getSize(size) // Размеры объекта bbox.getSize(size)
// Рассчитываем масштаб, чтобы объект полностью помещался в видимой области // Центрируем объект
const vFOV = degToRad(fov) // Угол обзора в радианах object.position.sub(center)
const visibleHeight = 2 * Math.tan(vFOV / 2) * distance // Видимая высота object.updateWorldMatrix(true, true)
const visibleWidth = visibleHeight * aspect // Видимая ширина
// Рассчитываем масштаб
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 scaleX = visibleWidth / size.x
const scaleY = visibleHeight / size.y const scaleY = visibleHeight / size.y
@ -40,18 +56,20 @@ export default function (
const scale = Math.min(scaleX, scaleY, scaleZ) const scale = Math.min(scaleX, scaleY, scaleZ)
// Позиционируем камеру с учетом смещения // Масштабируем объект
camera.position.set( object.scale.setScalar(scale)
center.x + distance * cameraOffset.x, object.updateWorldMatrix(true, true)
center.y + distance * cameraOffset.y,
center.z + distance * cameraOffset.z
)
// Устанавливаем цель для OrbitControls if (camera.value) {
if (controls) { const [x, y, z] = Object.values(cameraOffset).map(el => el * distance);
controls.target.set(center.x, center.y, center.z) camera.value.position.set(x, y, z);
controls.update() // Обновляем контроллы
} }
return { scale, center } // Настройка OrbitControls
if (controls.value) {
controls.value.target.set(...Object.values(center.multiplyScalar(1)))
controls.value.update()
}
return object
} }