Skip to content

Commit

Permalink
Restructure how grids specify their geometry
Browse files Browse the repository at this point in the history
Instead of just a set of points, grids specify a solid and a wireframe
for every side of every voxel. The grid is now free to specify UV
texture coordinates as appropriate, fixing a graphics problem with
optional voxel textures. This also opens the door for strangely shaped
grids in the future.
  • Loading branch information
mbrown1413 committed Apr 1, 2024
1 parent e00dbb0 commit 288a780
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 46 deletions.
12 changes: 7 additions & 5 deletions lib/Grid.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as THREE from "three"
import {Vector3} from "three"

import {SerializableClass} from "~/lib/serialize.ts"
Expand Down Expand Up @@ -32,12 +33,11 @@ export type VoxelInfo = {
voxel: Voxel,
shape: VoxelShape,
sides: Direction[],
}

/**
* Drawing information for each side. Each side must have an entry
* specifying the polygon to draw as a list of points in a line-loop.
*/
sidePolygons: {[key: Direction]: Vector3[]}
export type SideInfo = {
solid: THREE.BufferGeometry,
wireframe: Vector3[], // List of points in a line-loop
}

/**
Expand Down Expand Up @@ -187,6 +187,8 @@ export abstract class Grid extends SerializableClass {
*/
abstract getVoxels(bounds: Bounds): Voxel[]

abstract getSideInfo(voxel: Voxel, direction: Direction): SideInfo

/**
* Get voxel next to the given voxel in the given direction.
*
Expand Down
66 changes: 53 additions & 13 deletions lib/grids/CubicGrid.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Vector3, Matrix3} from "three"
import {Vector3, Matrix3, Matrix4, PlaneGeometry} from "three"

import {registerClass} from '~/lib/serialize.ts'
import {Grid, Bounds, Voxel, Viewpoint} from "~/lib/Grid.ts"
Expand All @@ -12,7 +12,6 @@ type CubicVoxelInfo = {
voxel: Voxel,
shape: CubicVoxelShape,
sides: Array<CubicDirection>,
sidePolygons: {[key in CubicDirection]: Vector3[]}
}

const CUBIC_DIRS: Array<CubicDirection> = [
Expand All @@ -35,6 +34,15 @@ const CUBIC_OPPOSITES: {[Property in CubicDirection]: CubicDirection} = {
"-Z": "+Z",
}

const SIDE_POLYGONS: {[side in CubicDirection]: [number, number, number][]} = {
"+X": [[1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 0, 1]],
"-X": [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 0, 1]],
"+Y": [[0, 1, 0], [1, 1, 0], [1, 1, 1], [0, 1, 1]],
"-Y": [[0, 0, 0], [1, 0, 0], [1, 0, 1], [0, 0, 1]],
"+Z": [[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]],
"-Z": [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]],
}

export class CubicGrid extends Grid {
private voxelToCoordinate(voxel: Voxel): Coordinate3d {
const coord = voxel.split(",").map(Number)
Expand All @@ -47,7 +55,7 @@ export class CubicGrid extends Grid {
z: coord[2]
}
}

private coordinateToVoxel(coordinate: Coordinate3d): Voxel {
return [coordinate.x, coordinate.y, coordinate.z].join(",")
}
Expand Down Expand Up @@ -84,20 +92,52 @@ export class CubicGrid extends Grid {
}

getVoxelInfo(voxel: Voxel): CubicVoxelInfo {
const {x, y, z} = this.voxelToCoordinate(voxel)
const v = (x: number, y: number, z: number) => new Vector3(x, y, z)
return {
voxel: voxel,
shape: "cube", // The only shape of voxel in this grid
sides: CUBIC_DIRS,
sidePolygons: {
"+X": [v(1+x, y, z), v(1+x, 1+y, z), v(1+x, 1+y, 1+z), v(1+x, y, 1+z)],
"-X": [v( x, y, z), v( x, 1+y, z), v( x, 1+y, 1+z), v( x, y, 1+z)],
"+Y": [v( x, 1+y, z), v(1+x, 1+y, z), v(1+x, 1+y, 1+z), v( x, 1+y, 1+z)],
"-Y": [v( x, y, z), v(1+x, y, z), v(1+x, y, 1+z), v( x, y, 1+z)],
"+Z": [v( x, y, 1+z), v(1+x, y, 1+z), v(1+x, 1+y, 1+z), v( x, 1+y, 1+z)],
"-Z": [v( x, y, z), v(1+x, y, z), v(1+x, 1+y, z), v( x, 1+y, z)],
}
}
}

getSideInfo(voxel: Voxel, direction: CubicDirection) {
const {x, y, z} = this.voxelToCoordinate(voxel)
const translation = new Vector3(x, y, z)
const wireframe = SIDE_POLYGONS[direction].map(
xyz => new Vector3(...xyz).add(translation)
)

// Construct transform for a plane for this side.
const transform = new Matrix4()

// Rotate along the plane normal purely so the UV texture coordinates
// line up between sides.
if(direction[0] === "+") {
transform.premultiply(new Matrix4().makeRotationZ(Math.PI/2))
}

// Translate plane from z axis going through the center to z axis going
// through the plane's corner.
transform.premultiply(new Matrix4().makeTranslation(0.5, 0.5, 0))

// Rotate to be on the X or Y plane if needed
switch(direction[1]) {
case "X": transform.premultiply(new Matrix4().makeRotationY(-Math.PI/2)); break
case "Y": transform.premultiply(new Matrix4().makeRotationX(Math.PI/2)); break
}

// Translate to + side of the cube if needed
transform.premultiply(new Matrix4().makeTranslation(
direction === "+X" ? 1 : 0,
direction === "+Y" ? 1 : 0,
direction === "+Z" ? 1 : 0
))

// Translate to this voxel's location
transform.premultiply(new Matrix4().makeTranslation(x, y, z))

return {
solid: new PlaneGeometry().applyMatrix4(transform),
wireframe,
}
}

Expand Down
50 changes: 24 additions & 26 deletions ui/components/GridDisplay_draw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import {ref, Ref, ComputedRef, onMounted, onUnmounted, computed, watchEffect, wa
import * as THREE from "three"
import {Vector3} from "three"
import {OrbitControls} from "three/addons/controls/OrbitControls.js"
import {ConvexGeometry} from "three/addons/geometries/ConvexGeometry.js"

import {VoxelInfo, Voxel, Viewpoint, Grid, Piece, Bounds, isColorSimilar, tweakColor} from "~lib"
import {VoxelInfo, Voxel, Viewpoint, Grid, Piece, Bounds, isColorSimilar, tweakColor, SideInfo} from "~lib"
import {Object3DCache, ResourceTracker} from "~/ui/utils/threejs.ts"
import {multiRenderer} from "~/ui/utils/MultiRenderer.ts"

Expand Down Expand Up @@ -194,14 +193,9 @@ export function useGridDrawComposible(
})

obj = new THREE.Object3D()
for(const polygon of Object.values(voxelInfo.sidePolygons)) {
const geometry = new ConvexGeometry(polygon)

// ConvexGeometry doesn't set UV values. This is a hack until we
// can do something better.
geometry.setAttribute("uv", geometry.getAttribute("position"))

const mesh = new THREE.Mesh(geometry, material)
for(const side of voxelInfo.sides) {
const sideInfo = grid.getSideInfo(voxelInfo.voxel, side)
const mesh = new THREE.Mesh(sideInfo.solid, material)
mesh.renderOrder = renderOrder
obj.add(mesh)
}
Expand All @@ -212,7 +206,7 @@ export function useGridDrawComposible(

function getVoxelThinWireframe(
piece: Piece | null,
voxelInfo: VoxelInfo,
sides: SideInfo[],
inLayer: boolean,
highlighted: boolean,
): THREE.Object3D {
Expand All @@ -224,9 +218,10 @@ export function useGridDrawComposible(
})

const obj = new THREE.Object3D()
for(const polygon of Object.values(voxelInfo.sidePolygons)) {
for(const sideInfo of sides) {
const geometry = new THREE.BufferGeometry()
geometry.setFromPoints(polygon)
geometry.setFromPoints(sideInfo.wireframe)

const line = new THREE.LineLoop(geometry, material)
line.renderOrder = renderOrder
obj.add(line)
Expand All @@ -236,7 +231,7 @@ export function useGridDrawComposible(

function getVoxelThickWireframe(
piece: Piece | null,
voxelInfo: VoxelInfo,
sides: SideInfo[],
inLayer: boolean,
highlighted: boolean,
): THREE.Object3D {
Expand All @@ -248,10 +243,10 @@ export function useGridDrawComposible(
const divisions = 4

const obj = new THREE.Object3D()
for(const polygon of Object.values(voxelInfo.sidePolygons)) {
for(let i=0; i<polygon.length; i++) {
const point1 = polygon[i]
const point2 = polygon[(i+1) % polygon.length]
for(const sideInfo of sides) {
for(let i=0; i<sideInfo.wireframe.length; i++) {
const point1 = sideInfo.wireframe[i]
const point2 = sideInfo.wireframe[(i+1) % sideInfo.wireframe.length]
const path = new THREE.LineCurve3(
new Vector3(...point1),
new Vector3(...point2)
Expand Down Expand Up @@ -299,13 +294,14 @@ export function useGridDrawComposible(

function getVoxelWireframe(
piece: Piece | null,
voxelInfo: VoxelInfo,
voxel: Voxel,
sides: SideInfo[],
inLayer: boolean,
highlighted: boolean,
): THREE.Object3D {
const key = JSON.stringify([
"wireframe",
voxelInfo.voxel,
voxel,
piece?.color,
inLayer,
highlighted,
Expand All @@ -314,9 +310,9 @@ export function useGridDrawComposible(
key,
() => {
if(inLayer) {
return getVoxelThickWireframe(piece, voxelInfo, inLayer, highlighted)
return getVoxelThickWireframe(piece, sides, inLayer, highlighted)
} else {
return getVoxelThinWireframe(piece, voxelInfo, inLayer, highlighted)
return getVoxelThinWireframe(piece, sides, inLayer, highlighted)
}
}
)
Expand Down Expand Up @@ -360,7 +356,8 @@ export function useGridDrawComposible(
}

if(!displayOnly || pieceAtVoxel) {
const wireframe = getVoxelWireframe(pieceAtVoxel, voxelInfo, inLayer, highlighted)
const sides = voxelInfo.sides.map(side => grid.getSideInfo(voxel, side))
const wireframe = getVoxelWireframe(pieceAtVoxel, voxel, sides, inLayer, highlighted)
scene.add(wireframe)
}

Expand Down Expand Up @@ -413,9 +410,10 @@ function getAllGridVertices(grid: Grid, bounds: Bounds): Vector3[] {
const points = []
for(const voxel of grid.getVoxels(bounds)) {
const voxelInfo = grid.getVoxelInfo(voxel)
for(const polygon of Object.values(voxelInfo.sidePolygons)) {
points.push(...polygon)
}
for(const side of voxelInfo.sides) {
const sideInfo = grid.getSideInfo(voxel, side)
points.push(...sideInfo.wireframe)
}
}
return points
}
Expand Down
3 changes: 1 addition & 2 deletions ui/components/GridDisplay_mouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ export function useGridMouseComposible(
function highlightObjectAtPosition(x: number, y: number) {
const intersectedObject = getObjectOnScreen(x, y)
if(intersectedObject) {
const newValue = intersectedObject.userData.voxel
highlightedVoxel.value = newValue
highlightedVoxel.value = intersectedObject.userData.voxel
} else {
highlightedVoxel.value = null
}
Expand Down

0 comments on commit 288a780

Please sign in to comment.