Skip to content

Instantly share code, notes, and snippets.

@ejfox
Created January 22, 2024 04:26
Show Gist options
  • Save ejfox/12bfcdaa3b09805f410ac0c1439b8a25 to your computer and use it in GitHub Desktop.
Save ejfox/12bfcdaa3b09805f410ac0c1439b8a25 to your computer and use it in GitHub Desktop.
Using anime, tresjs, and canvas to make rotating annotated objects in 3D
<script setup>
import { animate, createAnimatable, stagger } from '~/anime.esm.min.js'
import { TresCanvas, useLoader, extend } from '@tresjs/core'
import { createNoise3D } from 'simplex-noise';
import { EffectComposer, DepthOfField, Glitch, Noise, Outline, Pixelation, Vignette } from '@tresjs/post-processing'
import { Html, Line2 } from '@tresjs/cientos'
import * as THREE from 'three'
const { pressed } = useMousePressed()
const { x: mouseX, y: mouseY } = useMouse()
const originalSphereGroupRotation = { x: 0, y: 0, z: 0 }
const sphereGroupRotation = ref({ x: 0, y: 0, z: 0 })
const orbitGroupRotation = ref({ x: 0, y: 0, z: 0 })
const animSphereGroupRotation = createAnimatable(sphereGroupRotation.value, {
x: 200,
y: 0,
z: 0,
duration: 1000,
ease: 'out(10)'
})
const { onLoop } = useRenderLoop()
const { width, height } = useWindowSize()
const mult = 0.001
onLoop(() => {
// slowly orbit the orbit group around the center
orbitGroupRotation.value.y += 0.0075
// with animation
if (pressed.value) {
const chgAmtX = (mouseY.value - (width.value / 2)) * mult
const chgAmtY = (mouseX.value - (height.value / 2)) * mult
animSphereGroupRotation.x.to(chgAmtX)
animSphereGroupRotation.y.to(chgAmtY)
} else {
const multMult = 125
if (!animSphereGroupRotation) return
// nudge the rotation back to the original rotation
const currentX = sphereGroupRotation.value.x
const currentY = sphereGroupRotation.value.y
const currentZ = sphereGroupRotation.value.z
const targetX = originalSphereGroupRotation.x
const targetY = originalSphereGroupRotation.y
const targetZ = originalSphereGroupRotation.z
const diffX = targetX - currentX
const diffY = targetY - currentY
const diffZ = targetZ - currentZ
const chgAmtX = diffX * (mult * multMult)
const chgAmtY = diffY * (mult * multMult)
const chgAmtZ = diffZ * (mult * multMult)
animSphereGroupRotation.x.to(currentX + chgAmtX)
animSphereGroupRotation.y.to(currentY + chgAmtY)
animSphereGroupRotation.z.to(currentZ + chgAmtZ)
}
// draw circles on the canvas
if (annotationCtx.value) {
drawText(`x: ${sphereGroupRotation.value.x.toFixed(2)}, y: ${sphereGroupRotation.value.y.toFixed(2)}`)
}
})
const annotation = ref(null) // the canvas we will draw the annotation text in
const annotationCtx = ref(null) // the canvas context
const annotationTexture = ref(null) // the annotation texture we will apply to the plane
function drawText(textString) {
if (!annotationCtx.value) return
annotationCtx.value.clearRect(0, 0, annotation.value.width, annotation.value.height)
annotationCtx.value.fillStyle = 'white'
annotationCtx.value.font = 'bold 50px monospace'
annotationCtx.value.fillText(textString, 0, 50)
annotationTexture.value = new THREE.CanvasTexture(annotation.value)
}
onMounted(() => {
// get the canvas context
annotationCtx.value = annotation.value.getContext('2d')
// double the DPI of the canvas
const dpi = window.devicePixelRatio * 4
annotation.value.width = 100 * dpi
annotation.value.height = 100 * dpi
// create a texture from the canvas
annotationTexture.value = new THREE.CanvasTexture(annotation.value)
})
const orbitalObjects = ref([
{ offsetPosition: [4, 0, -0.2], color: '0x00ff00' },
{ offsetPosition: [-3.4, -3.4, 0], color: '0x0000ff' },
{ offsetPosition: [0, 1.1, 4], color: '0xff0000' },
{ offsetPosition: [0, -4, 0], color: '0x00ffff' },
{ offsetPosition: [-2, 1.4, -4], color: '0xffff00' },
]);
</script>
<template>
<div>
<canvas id="annotation" ref="annotation" class="hidden w-96 h-96 border border-red-500"></canvas>
<Suspense>
<div class="pointer-events-none">
<TresCanvas window-size shadow alpha>
<EffectComposer>
<Bloom :radius="4.85" :intensity="10" :luminance-threshold="0.04" :luminance-smoothing="0.3" mipmap-blur />
<Vignette />
<!-- <Pixelation :granularity="9" /> -->
</EffectComposer>
<!-- <TresPerspectiveCamera :position="[-0.2, 0, 5]" /> -->
<TresPerspectiveCamera :position="[-0.2, 0, 15]" />
<!-- <OrbitControls /> -->
<TresGroup :rotation="[sphereGroupRotation.x, sphereGroupRotation.y, sphereGroupRotation.z]">
<!-- <TransformControls :object="sphereRef" :size="2" /> -->
<TresMesh ref="sphereRef">
<TresSphereGeometry :args="[1, 16, 16]" />
<TresMeshStandardMaterial :color="0xff0000" />
</TresMesh>
<TresGroup :rotation="[orbitGroupRotation.x, orbitGroupRotation.y, orbitGroupRotation.z]">
<OrbitalObject v-for="(item, index) in orbitalObjects" :key="index" :offsetPosition="item.offsetPosition"
:color="item.color" />
<!-- and a line to the center -->
<TresMesh ref="lineRef2">
<!-- draw a line from the sphere to the annotation -->
<Line2 :points="[[0, 0], [4, 0]]" :line-width="2" color="#82dbc5" />
<TresLineBasicMaterial :color="'#ffffff'" />
</TresMesh>
<TresGroup :position="[4, 0, 0]">
<TresMesh ref="orbitRef">
<!-- offset the orbit group so that it is centered on the sphere -->
<TresSphereGeometry :args="[0.2, 8, 8]" />
<TresMeshStandardMaterial :color="0x00ff00" />
<TresMesh ref="annotationRef"
:rotation="[-orbitGroupRotation.x, -orbitGroupRotation.y, orbitGroupRotation.z]">
>
<TresPlaneGeometry :args="[2, 2]" />
<TresMeshBasicMaterial :map="annotationTexture" :transparent="true" />
</TresMesh>
</TresMesh>
</TresGroup>
</TresGroup>
<!-- draw annotation plane -->
<TresMesh ref="annotationPlaneRef">
<TresPlaneGeometry :args="[2, 3]" />
<TresMeshBasicMaterial :map="annotationTexture" :transparent="true" />
</TresMesh>
<TresMesh ref="lineRef">
<!-- draw a line from the sphere to the annotation -->
<Line2 :points="[[0, 0], [0, 1.2]]" :line-width="2" color="#82dbc5" />
<TresLineBasicMaterial :color="'#82dbc5'" />
</TresMesh>
</TresGroup>
<TresAmbientLight :intensity="3" />
<TresDirectionalLight :position="[0, 2, 4]" :intensity="10" />
</TresCanvas>
</div>
</Suspense>
</div>
</template>
<style scoped>
.label {
position: absolute;
transform-style: preserve-3d;
color: #000;
background: #fff;
padding: 5px;
border-radius: 5px;
}
</style>
<template>
<TresGroup :position="offsetPosition">
<!-- Line from this orbital object to the center -->
<TresMesh>
<Line2 :points="[[0, 0, 0], [-offsetPosition[0], -offsetPosition[1], -offsetPosition[2]]]" :line-width="2" color="#82dbc5" />
<TresLineBasicMaterial :color="'#ffffff'" />
</TresMesh>
<!-- Orbital Object Sphere -->
<TresMesh>
<TresSphereGeometry :args="[0.2, 8, 8]" />
<TresMeshStandardMaterial :color="color" />
</TresMesh>
<!-- Add Annotation Here -->
</TresGroup>
</template>
<script setup>
const props = defineProps({
offsetPosition: Array,
color: String,
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment