Skip to content

Commit

Permalink
Merge pull request #75 from M3-org/fix-cull-mesh-errors-3
Browse files Browse the repository at this point in the history
Fix cull mesh errors 3
  • Loading branch information
madjin authored Nov 24, 2023
2 parents 5731c4c + 0d379ed commit e5a6eb1
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 79 deletions.
7 changes: 1 addition & 6 deletions src/components/Scene.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,6 @@ export default function Scene({sceneModel, lookatManager}) {
const setOriginalInidicesAndColliders = () => {
avatarModel.traverse((child)=>{
if (child.isMesh) {
if (child.userData.lastBoundsTree){
child.userData.lastBoundsTree = child.geometry.boundsTree;
child.geometry.disposeBoundsTree();
}
if (child.userData.origIndexBuffer){
child.userData.clippedIndexGeometry = child.geometry.index.clone();
child.geometry.setIndex(child.userData.origIndexBuffer);
Expand All @@ -145,7 +141,6 @@ export default function Scene({sceneModel, lookatManager}) {
if (child.isMesh) {
if (child.userData.origIndexBuffer){
child.geometry.setIndex(child.userData.clippedIndexGeometry);
child.geometry.boundsTree = child.userData.lastBoundsTree;
}
}
})
Expand Down Expand Up @@ -193,7 +188,7 @@ export default function Scene({sceneModel, lookatManager}) {

const isCtrlPressed = event.ctrlKey;

const displayCullFaces = local["traitInformation_display_cull"] == null ? false : local["traitInformation_display_cull"];
const displayCullFaces = true;//local["traitInformation_display_cull"] == null ? false : local["traitInformation_display_cull"];
if (displayCullFaces){
setOriginalInidicesAndColliders();

Expand Down
10 changes: 1 addition & 9 deletions src/components/Selector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"
import { MToonMaterial, VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm"
import cancel from "../../public/ui/selector/cancel.png"
import { addModelData, disposeVRM } from "../library/utils"
import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast, SAH } from 'three-mesh-bvh';
import {ViewContext} from "../context/ViewContext"
import tick from "../../public/ui/selector/tick.svg"
import { AudioContext } from "../context/AudioContext"
import { SceneContext } from "../context/SceneContext"
import { SoundContext } from "../context/SoundContext"
import {
renameVRMBones,
createFaceNormals,
createBoneDirection,
} from "../library/utils"
import { LipSync } from '../library/lipsync'
Expand All @@ -26,9 +24,7 @@ import MenuTitle from "./MenuTitle"
import { saveVRMCollidersToUserData } from "../library/load-utils"


THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
THREE.Mesh.prototype.raycast = acceleratedRaycast;


export default function Selector({confirmDialog, uploadVRMURL, templateInfo, animationManager, blinkManager, lookatManager, effectManager}) {
const {
Expand Down Expand Up @@ -623,10 +619,6 @@ export default function Selector({confirmDialog, uploadVRMURL, templateInfo, ani
if (cullingIgnore.indexOf(child.name) === -1)
cullingMeshes.push(child)

if (child.geometry.boundsTree == null)
child.geometry.computeBoundsTree({strategy:SAH});

createFaceNormals(child.geometry)
if (child.isSkinnedMesh) {
createBoneDirection(child)
if (vrm.meta?.metaVersion === '0'){
Expand Down
23 changes: 0 additions & 23 deletions src/components/TraitInformation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export default function TraitInformation({animationManager, lookatManager}){
const [cullInDistance, setCullInDistance] = useState(0);
const [cullLayer, setCullLayer] = useState(0);
const [animationName, setAnimationName] = useState(animationManager.getCurrentAnimationName());
const [displayCullFaces, setDisplayCullFaces] = useState(local["traitInformation_display_cull"] == null ? false : local["traitInformation_display_cull"]);
const [hasMouseLook, setHasMouseLook] = useState(lookatManager.userActivated);

useEffect(() => {
Expand Down Expand Up @@ -74,12 +73,6 @@ export default function TraitInformation({animationManager, lookatManager}){
// Perform any additional actions or logic based on the checkbox state change
};

const handleDisplayCullFaces = (event) =>{
setDisplayCullFaces(event.target.checked);
local["traitInformation_display_cull"] = event.target.checked;
}


return (
displayTraitOption != null ? (
<div>
Expand Down Expand Up @@ -158,22 +151,6 @@ export default function TraitInformation({animationManager, lookatManager}){
</label>
</div>
</div>
<div className={styles["traitInfoText"]}>
<div className={styles["checkboxHolder"]}>
<div>

Display Hidden Faces on click
</div>
<label className={styles["custom-checkbox"]}>
<input
type="checkbox"
checked={displayCullFaces}
onChange={handleDisplayCullFaces}
/>
<div className={styles["checkbox-container"]}></div>
</label>
</div>
</div>

</div>

Expand Down
92 changes: 71 additions & 21 deletions src/library/cull-mesh.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { BufferAttribute, BackSide, FrontSide, Raycaster, Vector3, Color, BufferGeometry,LineBasicMaterial,Line, MeshBasicMaterial } from "three";
import { Mesh, Triangle, BufferAttribute, BackSide, FrontSide, Raycaster, Vector3, Color, BufferGeometry,LineBasicMaterial,Line, MeshBasicMaterial } from "three";
import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast, SAH } from 'three-mesh-bvh';

let origin = new Vector3();
let direction = new Vector3();
let worldScale = new Vector3();
let worldPosition = new Vector3();
const intersections = [];

const raycaster = new Raycaster();
Expand All @@ -16,6 +15,55 @@ const backMat = new MeshBasicMaterial({side:BackSide})

let mainScene;

BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
Mesh.prototype.raycast = acceleratedRaycast;

const createFaceNormals = (geometry) => {
const pos = geometry.attributes.position;
const idx = geometry.index;

const tri = new Triangle(); // for re-use
const a = new Vector3(), b = new Vector3(), c = new Vector3(); // for re-use

const faceNormals = [];

//set foreach vertex
for (let f = 0; f < (idx.array.length / 3); f++) {
const idxBase = f * 3;
a.fromBufferAttribute(pos, idx.getX(idxBase + 0));
b.fromBufferAttribute(pos, idx.getX(idxBase + 1));
c.fromBufferAttribute(pos, idx.getX(idxBase + 2));
tri.set(a, b, c);
faceNormals.push(tri.getNormal(new Vector3()));
}
geometry.userData.faceNormals = faceNormals;
}

const createCloneCullMesh = (mesh) => {
// clone mesh
const clonedGeometry = mesh.geometry.clone();
const clonedMaterial = mesh.material.clone();


// vrm0 mesh rotation
if (!mesh.userData.isVRM0){
const positions = clonedGeometry.attributes.position;
for (let i = 0; i < positions.array.length; i += 3) {
positions.array[i] = -positions.array[i]; // Flip x-coordinate
positions.array[i + 2] = -positions.array[i + 2]; // Flip z-coordinate
}
positions.needsUpdate = true;
}

const clonedMesh = new Mesh(clonedGeometry, clonedMaterial);

// bvh calculation
createFaceNormals(clonedMesh.geometry)
clonedMesh.geometry.computeBoundsTree({strategy:SAH});
return clonedMesh;
}

export const CullHiddenFaces = async(meshes) => {
// make a 2 dimensional array that will hold the layers
const meshData = [];
Expand All @@ -37,24 +85,27 @@ export const CullHiddenFaces = async(meshes) => {

// if it hasnt been previously created an array in this index value, create it
if (meshData[mesh.userData.cullLayer] == null){
meshData[mesh.userData.cullLayer] = {origMeshes:[], posMeshes:[], negMeshes:[], scaleMeshes:[], positionMeshes:[]}
meshData[mesh.userData.cullLayer] = {origMeshes:[], cloneMeshes:[], posMeshes:[], negMeshes:[], scaleMeshes:[], positionMeshes:[]}
}

mesh.getWorldScale(worldScale);
mesh.getWorldPosition(worldPosition);
meshData[mesh.userData.cullLayer].scaleMeshes.push(worldScale);
meshData[mesh.userData.cullLayer].positionMeshes.push(worldPosition);

if (mesh.userData.cullingClone == null){
mesh.userData.cullingClone = createCloneCullMesh(mesh);
mesh.userData.cullingCloneP = mesh.userData.cullingClone.clone();
mesh.userData.cullingCloneN = mesh.userData.cullingClone.clone();
}

// clone the mesh to only detect collisions in front faces
const cloneP = mesh.clone()
const clone = mesh.userData.cullingClone;
const cloneP = mesh.userData.cullingCloneP;
const cloneN = mesh.userData.cullingCloneN

cloneP.material = frontMat;
const cloneN = mesh.clone()
cloneN.userData.cancelMesh = cloneP;
cloneN.material = backMat;
cloneP.userData.maxCullDistance = cloneN.userData.maxCullDistance = mesh.userData.maxCullDistance;

meshData[mesh.userData.cullLayer].origMeshes.push(mesh)
meshData[mesh.userData.cullLayer].cloneMeshes.push(clone)
meshData[mesh.userData.cullLayer].posMeshes.push(cloneP)
meshData[mesh.userData.cullLayer].negMeshes.push(cloneN)

Expand All @@ -81,15 +132,14 @@ export const CullHiddenFaces = async(meshes) => {
for (let k = 0; k < meshData[i].origMeshes.length; k++){

const mesh = meshData[i].origMeshes[k];
const meshScale = meshData[i].scaleMeshes[k];
const meshPosition = meshData[i].positionMeshes[k];
const cloneMesh = meshData[i].cloneMeshes[k];
const index = mesh.userData.origIndexBuffer.array;
const vertexData = mesh.geometry.attributes.position.array;
const normalsData = mesh.geometry.attributes.normal.array;
const faceNormals = mesh.geometry.userData.faceNormals;
const vertexData = cloneMesh.geometry.attributes.position.array;
const normalsData = cloneMesh.geometry.attributes.normal.array;
const faceNormals = cloneMesh.geometry.userData.faceNormals;
geomsIndices.push({
geom: mesh.geometry,
index: getIndexBuffer(meshPosition,meshScale, index,vertexData,normalsData, faceNormals, hitArr,mesh.userData.cullDistance/*,i === 0*/)
index: getIndexBuffer(index,vertexData,normalsData, faceNormals, hitArr,mesh.userData.cullDistance/*,i === 0*/)
})
}
}
Expand Down Expand Up @@ -126,7 +176,7 @@ const getDistanceInOut = (distanceArr) => {
return [distIn, distOut]
}

const getIndexBuffer = (meshPosition, meshScale, index, vertexData, normalsData, faceNormals, intersectModels, distanceArr, debug = false) =>{
const getIndexBuffer = (index, vertexData, normalsData, faceNormals, intersectModels, distanceArr, debug = false) =>{

const indexCustomArr = [];
const distArr = getDistanceInOut(distanceArr);
Expand Down Expand Up @@ -158,9 +208,9 @@ const getIndexBuffer = (meshPosition, meshScale, index, vertexData, normalsData,

// move the origin away to have the raycast being casted from outside
origin.set(
(vertexData[vi] * meshScale.x ) + meshPosition.x,
(vertexData[vi+1] * meshScale.y ) + meshPosition.y,
(vertexData[vi+2] * meshScale.z) + meshPosition.z)
vertexData[vi],
vertexData[vi+1],
vertexData[vi+2])
.add(direction.clone().multiplyScalar(distIn))

//invert the direction of the raycaster as we moved it away from its origin
Expand Down
21 changes: 1 addition & 20 deletions src/library/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -429,26 +429,7 @@ export function disposeVRM(vrm) {

VRMUtils.deepDispose( model );
}
export const createFaceNormals = (geometry) => {
const pos = geometry.attributes.position;
const idx = geometry.index;

const tri = new THREE.Triangle(); // for re-use
const a = new THREE.Vector3(), b = new THREE.Vector3(), c = new THREE.Vector3(); // for re-use

const faceNormals = [];

//set foreach vertex
for (let f = 0; f < (idx.array.length / 3); f++) {
const idxBase = f * 3;
a.fromBufferAttribute(pos, idx.getX(idxBase + 0));
b.fromBufferAttribute(pos, idx.getX(idxBase + 1));
c.fromBufferAttribute(pos, idx.getX(idxBase + 2));
tri.set(a, b, c);
faceNormals.push(tri.getNormal(new THREE.Vector3()));
}
geometry.userData.faceNormals = faceNormals;
};

export const createBoneDirection = (skinMesh) => {
const geometry = skinMesh.geometry;

Expand Down

0 comments on commit e5a6eb1

Please sign in to comment.