Skip to content

Commit

Permalink
🏗️ Reworked the model builder
Browse files Browse the repository at this point in the history
  • Loading branch information
benc-uk committed Mar 5, 2024
1 parent 5ff5d36 commit cea2445
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 193 deletions.
118 changes: 45 additions & 73 deletions dist-single/gsots3d.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist-single/gsots3d.js.map

Large diffs are not rendered by default.

26 changes: 14 additions & 12 deletions examples/builder/main.mjs
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { Context, Material, RenderableBuilder } from '../../dist-single/gsots3d.js'
import { Context, Material, ModelBuilder } from '../../dist-single/gsots3d.js'

const ctx = await Context.init()
ctx.start()
ctx.setLogLevel('debug')

// Create a pyramid using RenderableBuilder
const builder = new RenderableBuilder()
const builder = new ModelBuilder()

const brick = Material.createBasicTexture('../_textures/brickwall.jpg', true)
const crate = Material.createBasicTexture('../_textures/STARG2.png', true)
const base = builder.newPart('pyramid-base', crate)
const sides = builder.newPart('pyramid-side', brick)
const brickMat = Material.createBasicTexture('../_textures/brickwall.jpg', true)
const crateMat = Material.createBasicTexture('../_textures/STARG2.png', true)
const basePart = builder.newPart('base', crateMat)
const sidesPart = builder.newPart('side', brickMat)

// Base on x z plane, anti-clockwise, reversed
base.addQuad([-1, 0, -1], [1, 0, -1], [1, 0, 1], [-1, 0, 1], [0, 0], [1, 0], [1, 1], [0, 1])
basePart.addQuad([-1, 0, -1], [1, 0, -1], [1, 0, 1], [-1, 0, 1], [0, 0], [1, 0], [1, 1], [0, 1])

// Four triangles as sides
sides.addTriangle([-1, 0, 1], [1, 0, 1], [0, 2, 0], [0, 0], [1, 0], [0.5, 1])
sides.addTriangle([1, 0, 1], [1, 0, -1], [0, 2, 0], [0, 0], [1, 0], [0.5, 1])
sides.addTriangle([1, 0, -1], [-1, 0, -1], [0, 2, 0], [0, 0], [1, 0], [0.5, 1])
sides.addTriangle([-1, 0, -1], [-1, 0, 1], [0, 2, 0], [0, 0], [1, 0], [0.5, 1])
sidesPart.addTriangle([-1, 0, 1], [1, 0, 1], [0, 2, 0], [0, 0], [1, 0], [0.5, 1])
sidesPart.addTriangle([1, 0, 1], [1, 0, -1], [0, 2, 0], [0, 0], [1, 0], [0.5, 1])
sidesPart.addTriangle([1, 0, -1], [-1, 0, -1], [0, 2, 0], [0, 0], [1, 0], [0.5, 1])
sidesPart.addTriangle([-1, 0, -1], [-1, 0, 1], [0, 2, 0], [0, 0], [1, 0], [0.5, 1])

const pyramid = ctx.createCustomInstance(builder)
ctx.buildCustomModel(builder, 'pyramid')
const pyramid = ctx.createModelInstance('pyramid')

ctx.camera.position = [0, 0, 6]
ctx.globalLight.setAsPosition(20, 70, 500)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gsots3d",
"version": "0.0.5-alpha.12",
"version": "0.0.5-alpha.13",
"description": "Getting S**t On The Screen in 3D. A library for doing 3D graphics in the browser.",
"author": "Ben Coleman",
"license": "MIT",
Expand Down
20 changes: 8 additions & 12 deletions src/core/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Model } from '../renderable/model.ts'
import { HUD } from './hud.ts'
import { Stats } from './stats.ts'
import { PostEffects } from '../engine/post-effects.ts'
import { RenderableBuilder } from '../renderable/builder.ts'
import { ModelBuilder } from '../renderable/builder.ts'

// Import shaders, tsup will inline these as text strings
import fragShaderPhong from '../../shaders/phong/glsl.frag'
Expand Down Expand Up @@ -798,19 +798,15 @@ export class Context {
}

/**
* Build a instance of a custom renderable from a builder and add it to the scene
* @param builder Builder with
* Create and build a custom model from a ModelBuilder and cache it for use
* @param builder Builder with geometry and materials added
* @param name Name of the model
*/
createCustomInstance(builder: RenderableBuilder) {
const renderable = builder.build(this.gl)
const instance = new Instance(renderable)
buildCustomModel(builder: ModelBuilder, name: string) {
const model = Model.parseFromBuilder(builder, name)

this.instances.set(instance.id, instance)
Stats.triangles += renderable.triangleCount
Stats.instances++

log.debug(`🗿 Created a custom renderable instance`)
log.info(`🔨 Custom model built and added to cache`)

return instance
ModelCache.instance.add(model)
}
}
102 changes: 14 additions & 88 deletions src/renderable/builder.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
// ===== builder.ts =========================================================
// Instance builder for creating custom renderable instances
// Model builder for creating custom models and multi-part geometry
// Ben Coleman, 2024
// ============================================================================

import * as twgl from 'twgl.js'
import { vec3 } from 'gl-matrix'

import { Renderable } from './types.ts'
import { ProgramCache } from '../core/cache.ts'
import { Stats } from '../core/stats.ts'
import { UniformSet } from '../core/gl.ts'
import { Material } from '../engine/material.ts'
import { XYZ } from '../engine/tuples.ts'
import { ModelPart } from './model.ts'

// TODO: No support for smooth shading, with shared vertices and normals

/**
* A builder for creating multi-part custom renderable instances from triangle meshes
* A builder for creating multi-part models from triangle meshes
* Use in conjunction Model.parseFromBuilder
*
* Example usage:
* ```typescript
* const builder = new RenderableBuilder()
* const builder = new ModelBuilder()
* const part = builder.newPart('foo' Material.RED)
* builder.addTriangle([1, -1, 1], [1, 1, 1], [-1, 1, 1])
* const renderable = builder.build(gl)
* Model.parseFromBuilder(builder, 'myModel')
* ```
*/
export class RenderableBuilder {
export class ModelBuilder {
public readonly parts: Map<string, BuilderPart>
private materials: Map<string, Material>
public readonly materials: Map<string, Material>

constructor() {
this.parts = new Map<string, BuilderPart>()
Expand All @@ -53,22 +50,6 @@ export class RenderableBuilder {

return builderPart
}

/**
* Called after all parts are ready, to generate a CustomRenderable
* @param gl A WebGL2RenderingContext
*/
build(gl: WebGL2RenderingContext): CustomRenderable {
const buffers = new Map<string, twgl.BufferInfo>()

for (const [name, builderPart] of this.parts) {
const partBuffers = builderPart.build(gl)
if (!partBuffers) continue
buffers.set(name, partBuffers)
}

return new CustomRenderable(buffers, this.materials)
}
}

/**
Expand All @@ -82,7 +63,11 @@ export class BuilderPart {
private normalData: number[] = []
private texcoordData: number[] = []

private triangleCount: number = 0
private _triCount: number = 0
public get triangleCount(): number {
return this._triCount
}

private _customArrayData: twgl.Arrays | undefined

private addVertex(x: number, y: number, z: number): number {
Expand All @@ -108,7 +93,7 @@ export class BuilderPart {
* @param v3 Vertex three of the triangle
*/
addTriangle(v1: XYZ, v2: XYZ, v3: XYZ, tc1 = [0, 0], tc2 = [0, 0], tc3 = [0, 0]) {
this.triangleCount++
this._triCount++
this.addVertex(v1[0], v1[1], v1[2])
this.addIndex()
this.addVertex(v2[0], v2[1], v2[2])
Expand Down Expand Up @@ -183,62 +168,3 @@ export class BuilderPart {
return bufferInfo
}
}

/**
* A custom renderable instance, created from a RenderableBuilder
*/
export class CustomRenderable implements Renderable {
private programInfo: twgl.ProgramInfo = ProgramCache.instance.default
private _triangleCount: number = 0
private modelParts: ModelPart[]

// List of materials mapped by name
public readonly materials: Map<string, Material>

constructor(bufferInfos: Map<string, twgl.BufferInfo>, materials: Map<string, Material>) {
this.modelParts = new Array<ModelPart>()
this.materials = materials

for (const [name, bi] of bufferInfos) {
const p = new ModelPart(bi, name)
this.modelParts.push(p)
}
}

/**
* Render is used draw this custom renderable, this is called from the Instance that wraps
* this renderable.
*/
render(
gl: WebGL2RenderingContext,
uniforms: UniformSet,
materialOverride?: Material,
programOverride?: twgl.ProgramInfo,
) {
const programInfo = programOverride || this.programInfo
gl.useProgram(programInfo.program)

// Render all parts
for (const part of this.modelParts) {
const bufferInfo = part.bufferInfo

if (materialOverride === undefined) {
const material = this.materials.get(part.materialName)
if (!material) continue
material.apply(programInfo)
} else {
materialOverride.apply(programInfo)
}

twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo)
twgl.setUniforms(programInfo, uniforms)

twgl.drawBufferInfo(gl, bufferInfo)
Stats.drawCallsPerFrame++
}
}

get triangleCount(): number {
return this._triangleCount
}
}
41 changes: 37 additions & 4 deletions src/renderable/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getGl, UniformSet } from '../core/gl.ts'
import { Renderable } from './types.ts'
import { Stats } from '../core/stats.ts'
import { ProgramCache } from '../core/cache.ts'
import { ModelBuilder } from './builder.ts'

/**
* Holds a 3D model, as a list of parts, each with a material
Expand All @@ -24,7 +25,7 @@ export class Model implements Renderable {
private programInfo: twgl.ProgramInfo
private readonly parts = [] as ModelPart[]
private readonly materials = {} as Record<string, Material>
private triangles: number
private triCount: number
private _boundingBox: number[]

/** Name of the model, usually the filename without the extension */
Expand All @@ -35,7 +36,7 @@ export class Model implements Renderable {
*/
private constructor(name: string) {
this.name = name
this.triangles = 0
this.triCount = 0
this.programInfo = ProgramCache.instance.default
this._boundingBox = [
Number.MAX_VALUE,
Expand Down Expand Up @@ -88,7 +89,7 @@ export class Model implements Renderable {

/** Simple getter for the number of triangles in the model */
get triangleCount(): number {
return this.triangles
return this.triCount
}

/**
Expand Down Expand Up @@ -174,7 +175,39 @@ export class Model implements Renderable {
} materials in ${((performance.now() - startTime) / 1000).toFixed(2)}s`,
)

model.triangles = objData.triangles
model.triCount = objData.triangles
return model
}

/**
* Parse a custom model from a ModelBuilder, this is used to build models in code
* @param {ModelBuilder} builder - The ModelBuilder to parse
* @param {string} name - The name of the model
*/
static parseFromBuilder(builder: ModelBuilder, name: string) {
const model = new Model(name)

const gl = getGl()
if (!gl) {
throw new Error('💥 Unable to get WebGL context')
}

// Fall back default material
model.materials.__default = new Material()
model.materials.__default.diffuse = [0.1, 0.6, 0.9]

for (const [partName, builderPart] of builder.parts) {
// Build the buffers for this part
const partBuffers = builderPart.build(gl)
if (!partBuffers) continue

model.triCount += builderPart.triangleCount
model.parts.push(new ModelPart(partBuffers, partName))
model.materials[partName] = builder.materials.get(partName) ?? model.materials.__default
}

log.debug(`♟️ Model '${name}' built with ${model.parts.length} parts & ${model.triCount} triangles`)

return model
}

Expand Down

0 comments on commit cea2445

Please sign in to comment.