3d
A babylonjs wrapper.
A <tosi-3d> element is initialized with an engine, canvas, scene, and an update-loop.
If you view this example with an XR-enabled device, such as the Meta Quest 3, then you should be able to view this as an AR scene.
import { b3d, gamepadText, xrControllers, xrControllersText } from 'tosijs-ui'
preview.append(b3d({
async sceneCreated(element, BABYLON) {
const camera = new BABYLON.FreeCamera(
'camera',
new BABYLON.Vector3(0, 1, -4),
element.scene
)
camera.attachControl(element.parts.canvas, true)
new BABYLON.HemisphericLight('light', new BABYLON.Vector3(0.25, 1, -0.5), element.scene)
this.loadScene('/', 'xin3d.glb')
const size = 1024
const textTexture = new BABYLON.DynamicTexture('Text', size, element.scene)
const textContext = textTexture.getContext()
textTexture.update()
const textMaterial = new BABYLON.StandardMaterial('Text', element.scene)
textMaterial.diffuseTexture = textTexture
textMaterial.emissiveTexture = textTexture
textMaterial.backfaceCulling = false
const plaque = BABYLON.MeshBuilder.CreatePlane('Plaque', {size: 1}, element.scene)
plaque.position.x = 0
plaque.position.y = 2
plaque.billboardMode = BABYLON.Mesh.BILLBOARDMODE_ALL
plaque.material = textMaterial
let controllers
if (navigator.xr) {
const xrHelper = await element.scene.createDefaultXRExperienceAsync({
uiOptions: {
sessionMode: 'immersive-ar'
}
})
controllers = xrControllers(xrHelper)
}
const interval = setInterval(() => {
if (document.body.contains(element)) {
textContext.fillStyle = '#204020'
textContext.fillRect(0, 0, size, size)
const text = gamepadText() + '\n' + xrControllersText(controllers)
const lines = text.split('\n')
textContext.fillStyle = '#afa'
textContext.font = '32px monospace'
for(let i = 0; i < lines.length; i++) {
const line = lines[i]
textContext.fillText(line, 40, 70 + i * 40)
}
textContext.fillStyle = '#bbb'
textContext.fillText('tosijs-xr — debug info', 40, 984)
textTexture.update()
} else {
clearInterval(interval)
}
}, 100)
},
}))
.preview tosi-3d {
width: 100%;
height: 100%;
}
Note: for more involved 3d work there's tosijs-3d, a library of modular components for 3d development.
You can access the scene and engine properties. You can also assign sceneCreated
and update callbacks that will be executed when the scene is first initialized and
before each update, respectively. (See the example, it does both.)
Both sceneCreated and update may be async. The component will await sceneCreated
before starting the renderLoop, but update is simply passed to babylon, so be careful.
By default, this component loads babylon.js from the babylonjs CDN,
but if BABYLON is already defined (e.g. if you've bundled it) then it will use that instead.
If you need additional libraries, e.g. https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js for loading models such as gltf and glb files, you should load those in sceneCreated.
Here's a simple example of a terrain mesh comprising 125k triangles, 50% of which is being scaled using a profileScale function that
takes an array of numbers that use a linear profile to change the landform.
import { b3d } from 'tosijs-ui'
import { MoreMath } from 'tosijs'
const debugCutoff = 0.5
const defaultProfile = [0, 1, 5, 8, 10].map(x => x/10)
const { clamp } = MoreMath
function profileScale(t = 0, bypass = false, profile = defaultProfile) {
if (bypass) {
return t
}
const count = profile.length - 1
if (count < 1) {
throw new Error('profile must be of length ≥ 2')
}
const s = clamp(0, (t + 1) / 2, 1)
const index = Math.floor(s * count)
const dt = (s - index / count) * count
const min = profile[index]
const max = profile[index + 1]
const p = dt * (max - min) + min
return 2 * p - 1
}
preview.append(b3d({
async sceneCreated(element, BABYLON) {
const { scene } = element
const { createNoise2D } = await import('https://cdn.jsdelivr.net/npm/simplex-noise@4.0.1/+esm')
new BABYLON.HemisphericLight('light', new BABYLON.Vector3(0.25, 1, 2), scene)
const terrain = new BABYLON.Mesh('terrain', scene)
const vertexData = new BABYLON.VertexData()
const noise2D = createNoise2D()
const positions = []
const indices = []
const gridSize = 100
const gridResolution = 250
const gridPoints = gridResolution + 1
const noiseScale = 0.03
const heightScale = 4.5
terrain.position.y = -5
const scale = t => t * gridSize / gridResolution - gridSize * 0.5
for(let x = 0; x <= gridResolution; x++) {
for(let z = 0; z <= gridResolution; z++) {
const y = profileScale(noise2D(scale(x) * noiseScale, scale(z) * noiseScale), x < gridResolution * debugCutoff)
positions.push(scale(x), y * heightScale, scale(z))
if (x > 0 && z > 0) {
const i = x * gridPoints + z
indices.push(
i, i - gridPoints - 1, i - 1,
i, i - gridPoints, i - gridPoints - 1,
)
}
}
}
const normals = []
BABYLON.VertexData.ComputeNormals(positions, indices, normals);
vertexData.positions = positions
vertexData.indices = indices
vertexData.normals = normals
vertexData.applyToMesh(terrain)
},
}))
Pure-HTML scenes (attributes)
<tosi-3d> can assemble a scene entirely from HTML attributes — no sceneCreated
callback, no JavaScript — so a 3D hero can be dropped into a declarative page. Set
src and it works like an <img>; a default camera and lighting are added when
the scene doesn't provide its own, so a model shows up out of the box.
src— a.glbURL; loads on connect (callsloadScenefor you).hero-light— adds a directional key light (front-upper-left) over the soft hemispheric fill, for a "product photography" look.fov— field-of-view multiplier on the camera's natural lens:1(default) leaves it alone,<1is a longer lens (less perspective distortion),>1wider.clear-color— scene background: a css hex color, ortransparentto composite the 3D over the page (great for themed narrative pages).
This loads the same scene as the example above, in plain HTML, with a hero key light and a slightly longer lens:
<tosi-3d src="/xin3d.glb" hero-light fov="0.6"></tosi-3d>
.preview tosi-3d {
width: 100%;
height: 100%;
}
loadScene
<tosi-3d>.loadScene(path: string, file: string, callBack(meshes: any[]): void) can
be used to load .glb files. Setting src calls this for you on connect.
loadUI
<tosi-3d>.loadUI(options: B3dUIOptions) loads babylonjs guis, which you can create programmatically or using the babylonjs gui tool.