The following 3D brush painting algorithm integrates depth control for VR experiences by leveraging p5.treegl’s parsePosition function. This allows users to paint dynamically in 3D space, using mouse input and a depth slider to place brush strokes with precision.

code
'use strict'

let color, depth, brush, escorzo = true, fallback = [], points = [], record

function preload() {
  loadJSON('cloud_500.json', json =>
    fallback = json.map(entry => ({
      worldPosition: createVector(entry.x, entry.y, entry.z),
      color: entry.color
    }))
  )
}

function setup() {
  createCanvas(600, 400, WEBGL)
  colorMode(RGB, 1)
  document.oncontextmenu = () => false
  points = [...fallback]
  const o = parsePosition(Tree.ORIGIN, { from: Tree.WORLD, to: Tree.SCREEN })
  depth = createSlider(0, 1, o.z, 0.001)
  depth.position(10, 10)
  depth.style('width', '580px')
  color = createColorPicker('#C7C08D')
  color.position(width - 70, 40)
  brush = sphereBrush
}

function draw() {
  (mouseY >= 30) && orbitControl()
  record && update()
  background('#222226')
  axes({ size: 50, bits: Tree.X | Tree.Y | Tree.Z })
  for (const point of points) {
    push()
    translate(point.worldPosition)
    brush(point)
    pop()
  }
}

function keyPressed() {
  key === 'c' && (points = [])
  key === 'f' && focus()
  key === 'r' && (record = !record)
  key === 's' && saveCloud()
}

function sphereBrush(point) {
  push()
  noStroke()
  fill(point.color)
  sphere(1)
  pop()
}

function update() {
  points.push({
    worldPosition: parsePosition([mouseX, mouseY, depth.value()], { from: Tree.SCREEN, to: Tree.WORLD }),
    color: color.color(),
  })
}

function focus() {
  const center = [0, 0, 0]
  const position = parsePosition()
  const up = parseDirection(Tree.j)
  camera(...position.array(), ...center, ...up.array())
  const o = parsePosition(Tree.ORIGIN, { from: Tree.WORLD, to: Tree.SCREEN })
  depth.value(o.z)
}

function saveCloud() {
  const data = points.map(point => ({
    x: point.worldPosition.x,
    y: point.worldPosition.y,
    z: point.worldPosition.z,
    color: [red(point.color) / 255, green(point.color) / 255, blue(point.color) / 255, alpha(point.color) / 255]
  }))
  saveJSON(data, 'custom_cloud.json')
}

Key Elements of the 3D Brush Algorithm

The primary functionality revolves around transforming user input from screen space to world space, enabling dynamic point placement. Using the parsePosition function, we map 2D screen coordinates and depth values into the 3D world, allowing the brush to paint accurately in VR space.

Depth Control with parsePosition

The depth slider controls the z-axis positioning by transforming screen space to world space. This is key to painting in depth and makes the VR brush interactive.

function update() {
  points.push({
    worldPosition: parsePosition([mouseX, mouseY, depth.value()], { from: Tree.SCREEN, to: Tree.WORLD }),
    color: color.color(),
  })
}

Setting the World Origin as the Focal Point

The focus function orients the camera towards the center of the 3D world without reposition it, using parseDirection(Tree.j) to align the camera’s up direction. By recalibrating the depth slider to reflect the z-coordinate of the world origin in screen space, it allows the user to precisely draw points passing through the world origin.

function focus() {
  const center = [0, 0, 0]
  const position = parsePosition()
  const up = parseDirection(Tree.j)
  camera(...position.array(), ...center, ...up.array())
  const o = parsePosition(Tree.ORIGIN, { from: Tree.WORLD, to: Tree.SCREEN })
  depth.value(o.z)
}

Brush Strokes in 3D

Each brush stroke is rendered in 3D, using sphereBrush to draw small spheres at the calculated positions.

function sphereBrush(point) {
  push()
  noStroke()
  fill(point.color)
  sphere(1)
  pop()
}

Camera Controls and Recording

  • Camera Navigation: Users can freely move around the scene using orbitControl.
  • Recording & Saving: The keyPressed function toggles recording, and users can save their custom point clouds as JSON.

p5.treegl API reference

For more details on parsePosition and the p5.treegl library, check out:

Further reading

  • Tilt Brush by Google – A VR painting tool that allows users to paint in 3D space using virtual reality controllers.
  • Quill by Oculus – A VR illustration and animation tool for immersive 3D storytelling.
  • Gravity Sketch – A 3D design tool for VR that allows users to sketch and create 3D models directly in the VR environment.

This project showcases the seamless integration of p5.js and p5.treegl for VR experiences, where intuitive depth control adds a whole new dimension to interactive 3D painting.