fenomas / noa

Experimental voxel game engine.
MIT License
608 stars 86 forks source link

object meshing bug #183

Closed MCArth closed 1 year ago

MCArth commented 1 year ago

Hi, I've brought in the changes and I'm encountering an object meshing bug. Occasionally an object will just appear at local position 0, 0, 0 (so it follows you around). When this happens, placing an object of that type results in no object thin instance being placed inside the placed block position (so I'm theorising the object at 0, 0, 0 is lots of the object stacked on top of each other). I've encountered this on both babylon 5.14.1 and 5.25.0.

image

I know there were changes as a result of changes to babylon internals, perhaps this is related.

I've also noticed now when I first open the world, object meshes briefly flash visible on the screen (also at 0, 0, 0 local I think). This isn't such a big deal but may also be related. Unsure when the thin instances are placed at the correct position, if there's a gap that could explain the brief flashing.

fenomas commented 1 year ago

Hmm, I've not seen either of these. Can you share the code you're using to create the mesh you're using, and the code where you register it as an object block?

MCArth commented 1 year ago

I've been able the object at local 0,0,0 in stress: image

With the following code:


import { Engine } from 'noa-engine'
// registration
import atlasURL from '../textures/terrain_atlas.png'
import tallgrass from '../textures/tallgrass.png'

import { Mesh, PBRMaterial, PlaneBuilder, Texture } from '@babylonjs/core'

var noa = new Engine({
    debug: true,
    showFPS: true,
    // inverseY: true,
    chunkSize: 48,
    chunkAddDistance: [1, 1],
    playerStart: [0, 10, 0],
    playerAutoStep: true,
})

noa.registry.registerMaterial('grass', { textureURL: atlasURL, atlasIndex: 0 })
noa.registry.registerMaterial('g_dirt', { textureURL: atlasURL, atlasIndex: 1 })
noa.registry.registerMaterial('dirt', { textureURL: atlasURL, atlasIndex: 2 })
noa.registry.registerMaterial('stone', { textureURL: atlasURL, atlasIndex: 3 })
noa.registry.registerMaterial('stone2', { textureURL: atlasURL, atlasIndex: 4 })
noa.registry.registerMaterial('cloud', { textureURL: atlasURL, atlasIndex: 5 })
noa.registry.registerMaterial('seethrough', { textureURL: atlasURL, atlasIndex: 6, texHasAlpha: true })

var id = 1
var dirt = noa.registry.registerBlock(id++, { material: 'dirt' })
var grass = noa.registry.registerBlock(id++, { material: 'grass' })
var stone = noa.registry.registerBlock(id++, { material: 'stone' })
var stone2 = noa.registry.registerBlock(id++, { material: 'stone2' })
// var cloud = noa.registry.registerBlock(id++, { material: 'cloud', opaque: false })
// var seethrough = noa.registry.registerBlock(id++, { material: 'seethrough', opaque: false })
var centrecross = noa.registry.registerBlock(id++, { 
    blockMesh: getCentreCrossModel(noa, tallgrass), 
    opaque: true,
    solid: true,
    onCustomMeshCreate: null,
})

function getCentreCrossModel(noa, tex) {
    const scene = noa.rendering.getScene()

    const plane1 = PlaneBuilder.CreatePlane(
        `BlkMdl1`,
        {
            size: 1,
            sideOrientation: Mesh.DOUBLESIDE,
        },
        scene
    )
    plane1.isPickable = false
    plane1.rotation.x = Math.PI
    plane1.position.y = 0.5
    plane1.bakeCurrentTransformIntoVertices()
    plane1.setEnabled(false)

    const plane2 = PlaneBuilder.CreatePlane(
        `BlkMdl2`,
        {size: 1, sideOrientation: Mesh.DOUBLESIDE},
        scene
    )
    plane2.isPickable = false

    plane2.rotation.y = Math.PI / 2
    plane2.rotation.z = Math.PI
    plane2.position.y = 0.5
    plane2.bakeCurrentTransformIntoVertices()
    plane2.setEnabled(false)

    const mat = new PBRMaterial(`BlockModelMeshMat`, scene)
    const texture = new Texture(tex, scene, null, null, 1)
    mat.unlit = true
    texture.hasAlpha = true
    mat.albedoTexture = texture
    mat.freeze()

    plane1.material = mat
    plane2.material = mat

    const mergedMesh = Mesh.MergeMeshes([plane1, plane2], true, false, null, false, true)
    mergedMesh.isPickable = false
    plane1.dispose()
    plane2.dispose()

    return mergedMesh
}

// worldgen
var decideVoxel = (x, y, z, ht, clo, chi, pillar) => {
    let id = 1+Math.floor(Math.random()*4)
    // if (Math.random() < 0.00002) {
        // id = 5
    // }
    if (y < ht) {
        return id
    }
    if (y < ht + pillar) return id
    if (y > clo && y < chi) return id

    if (y < ht+1 && Math.random() < 0.0002) {
        return 5
    }

    return 0
}
noa.world.on('worldDataNeeded', async (requestID, data, cx, cy, cz) => {
    await waitMs(1000)
    for (var i = 0; i < data.shape[0]; i++) {
        var x = cx + i
        for (var k = 0; k < data.shape[2]; k++) {
            var z = cz + k
            var ht = Math.sqrt(x * x + z * z) / 100
            var a = noise(x, 150)
            var b = noise(z + 50, 140)
            var c = noise(x - z - 50, 120)
            ht += 2 * a + b + c
            var pillar = (Math.random() < 0.002) ? 5 : 0
            var clo = 39 + 2 * (b - c)
            var chi = 35 + 2 * (a - b)
            for (var j = 0; j < data.shape[1]; j++) {
                var y = cy + j
                var id = decideVoxel(x, y, z, ht, clo, chi, pillar)
                data.set(i, j, k, id)
            }
        }
    }
    // tell noa the chunk's terrain data is now set
    noa.world.setChunkData(requestID, data)
})

async function waitMs(ms) {
    return new Promise(((resolve, reject) => {
        setTimeout(() => {
            resolve()
        }, ms)
    }))
}

add the following texture: tallgrass

to repro just fly backwards on first load. you might have to do it a few times to get it to repro

(i havent seen the "briefly flashing on screen" bit, but hopefully that's related and can be fixed simultaneously)

MCArth commented 1 year ago

https://streamable.com/cohlbf

fenomas commented 1 year ago

Hey, thanks very much for the complete repro steps. This was really thorny, it seems to have been a timing issue with Babylon internals, which depended on the timing of the call to mat.freeze() (but only in the case of PBR materials).

I've pushed a fix that seems to resolve it - can you check? I was only really able to reproduce the issue sporadically, so it's hard to be sure about a fix.

MCArth commented 1 year ago

Thanks for looking into it. It's always the race conditions that get you 😅

Seems to fix it for me as well

something to consider: it could be good to lock to a specific babylon version in both noa and the examples (or at least only allow patch upgrades with ~). An awful amount of things seem to break with babylon updates

fenomas commented 1 year ago

Thanks for checking! Let's hope this is the last we see of this one...