@xrengine/server-core
Version:
Shared components for XREngine server
116 lines (100 loc) • 3.9 kB
text/typescript
import {
Box3,
DirectionalLight,
HemisphereLight,
PerspectiveCamera,
Scene,
sRGBEncoding,
Vector3,
WebGLRenderer
} from 'three'
import {
MAX_ALLOWED_TRIANGLES,
THUMBNAIL_HEIGHT,
THUMBNAIL_WIDTH
} from '@xrengine/common/src/constants/AvatarConstants'
import { createGLTFLoader } from '@xrengine/engine/src/assets/functions/createGLTFLoader'
import { loadDRACODecoder } from '@xrengine/engine/src/assets/loaders/gltf/NodeDracoLoader'
import logger from '../../ServerLogger'
/**
* @todo gl is problematic, we need to look into a better way to handle this
*/
let camera: PerspectiveCamera, scene: Scene, renderer: WebGLRenderer, loader, canvas, context
const toArrayBuffer = (buf) => {
const arrayBuffer = new ArrayBuffer(buf.length)
const view = new Uint8Array(arrayBuffer)
for (let i = 0; i < buf.length; ++i) {
view[i] = buf[i]
}
return arrayBuffer
}
const createThreeScene = () => {
camera = new PerspectiveCamera(45, THUMBNAIL_WIDTH / THUMBNAIL_HEIGHT, 0.1, 200)
camera.position.set(0, 1.25, 2)
camera.lookAt(0, 1.25, 0)
camera.rotateZ(Math.PI)
scene = new Scene()
const backLight = new DirectionalLight(0xfafaff, 1)
backLight.position.set(1, 3, -1)
backLight.target.position.set(0, 1.5, 0)
const frontLight = new DirectionalLight(0xfafaff, 0.7)
frontLight.position.set(-1, 3, 1)
frontLight.target.position.set(0, 1.5, 0)
const hemi = new HemisphereLight(0xeeeeff, 0xebbf2c, 1)
scene.add(backLight)
scene.add(backLight.target)
scene.add(frontLight)
scene.add(frontLight.target)
scene.add(hemi)
//TODO
// canvas = createCanvas(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
canvas.addEventListener = () => {} // mock function to avoid errors inside THREE.WebGlRenderer()
// context = gl(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, { preserveDrawingBuffer: true })
logger.info({ canvas, context })
renderer = new WebGLRenderer({
canvas,
// context,
antialias: true,
preserveDrawingBuffer: true,
alpha: true
})
renderer.autoClear = false
renderer.setClearColor(0xffffff, 1)
renderer.setPixelRatio(1)
renderer.setSize(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, false)
renderer.outputEncoding = sRGBEncoding
loader = createGLTFLoader(true)
}
export const generateAvatarThumbnail = async (avatarModel: Buffer): Promise<Buffer> => {
if (!renderer) createThreeScene()
await loadDRACODecoder()
const model: any = await new Promise((resolve, reject) =>
loader.parse(toArrayBuffer(avatarModel), '', resolve, reject)
)
scene.add(model.scene)
renderer.render(scene, camera)
scene.remove(model.scene)
validate(model.scene)
const outputBuffer = canvas.toBuffer()
// const pixels = new Uint8Array(THUMBNAIL_WIDTH * THUMBNAIL_HEIGHT * 4)
// context.readPixels(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, context.RGBA, context.UNSIGNED_BYTE, pixels)
// const outputBuffer = Buffer.from(encode(pixels.buffer, [THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT], 'png'))
return outputBuffer
}
const maxBB = new Vector3(2, 2, 2)
const validate = (model) => {
if (renderer.info.render.triangles > MAX_ALLOWED_TRIANGLES)
throw new Error(`Max allowed triangles of ${MAX_ALLOWED_TRIANGLES} surpassed, try another avatar`)
if (renderer.info.render.triangles <= 0) throw new Error(`Avatar model seems empty, try another avatar`)
const objBoundingBox = new Box3().setFromObject(model)
const size = new Vector3().subVectors(maxBB, objBoundingBox.getSize(new Vector3()))
if (size.x <= 0 || size.y <= 0 || size.z <= 0)
throw new Error(`Avatar model seems too big or off center, check the model file`)
let bone = false
let skinnedMesh = false
model.traverse((o) => {
if (o.type.toLowerCase() === 'bone') bone = true
if (o.type.toLowerCase() === 'skinnedmesh') skinnedMesh = true
})
if (!bone || !skinnedMesh) throw new Error(`Avatar skeleton not detected, check the model files`)
}