Can anone help me with this i am trying to get a portal effect working using [barnabasbartha] method and i have some problem in oblique projection
import * as THREE from "three"
import CANNON from "cannon"
import Game from "./game"
class Portal {
constructor(scene, renderer) {
this.scene = scene;
this.renderer = renderer;
this.tmpScene = new THREE.Scene();
this.rotationYMatrix = new THREE.Matrix4().makeRotationY(Math.PI);
this.inverse = new THREE.Matrix4();
this.dstInverse = new THREE.Matrix4();
this.srcToCam = new THREE.Matrix4();
this.srcToDst = new THREE.Matrix4();
this.result = new THREE.Matrix4();
this.dstRotationMatrix = new THREE.Matrix4();
this.normal = new THREE.Vector3();
this.clipPlane = new THREE.Plane();
this.clipVector = new THREE.Vector4();
this.q = new THREE.Vector4();
this.projectionMatrix = new THREE.Matrix4();
this.cameraInverseViewMat = new THREE.Matrix4();
this.originalCameraMatrixWorld = new THREE.Matrix4();
this.originalCameraProjectionMatrix = new THREE.Matrix4();
this.maxRecursion = 1;
}
computePortalViewMatrix(sourcePortal,destinationPortal, viewMat){
this.srcToCam.multiplyMatrices(this.inverse.copy(viewMat).invert(), sourcePortal.matrixWorld.clone());
this.dstInverse.copy(destinationPortal.matrixWorld.clone()).invert();
this.srcToDst.identity().multiply(this.srcToCam).multiply(this.rotationYMatrix).multiply(this.dstInverse);
this.result.copy(this.srcToDst).invert();
return this.result;
}
computePortalProjectionMatrix(destinationPortal, viewMat, projMat){
this.cameraInverseViewMat.copy(viewMat).invert();
this.dstRotationMatrix.identity().extractRotation(destinationPortal.matrixWorld);
// TODO: Use -1 if dot product is negative (?)
this.normal.set(0, 0, 1).applyMatrix4(this.dstRotationMatrix);
this.clipPlane.setFromNormalAndCoplanarPoint(this.normal, destinationPortal.position);
this.clipPlane.applyMatrix4(this.cameraInverseViewMat);
this.clipVector.set(
this.clipPlane.normal.x,
this.clipPlane.normal.y,
this.clipPlane.normal.z,
this.clipPlane.constant,
);
this.projectionMatrix.copy(projMat);
this.q.x = (Math.sign(this.clipVector.x) + this.projectionMatrix.elements[8]) / this.projectionMatrix.elements[0];
this.q.y = (Math.sign(this.clipVector.y) + this.projectionMatrix.elements[9]) / this.projectionMatrix.elements[5];
this.q.z = -1.0;
this.q.w = (1.0 + this.projectionMatrix.elements[10]) / projMat.elements[14];
this.clipVector.multiplyScalar(2 / this.clipVector.dot(this.q));
this.projectionMatrix.elements[2] = this.clipVector.x;
this.projectionMatrix.elements[6] = this.clipVector.y;
this.projectionMatrix.elements[10] = this.clipVector.z + 1;
this.projectionMatrix.elements[14] = this.clipVector.w
return this.projectionMatrix;
}
worldToLocal(object, vector) {
const worldInverse = new THREE.Matrix4().copy(object.matrixWorld).invert();
return vector.clone().applyMatrix4(worldInverse);
}
localToWorld(object, vector) {
return vector.clone().applyMatrix4(object.matrixWorld);
}
renderScene(camera, children, viewMat, projMat) {
this.tmpScene.children = children;
this.originalCameraMatrixWorld.copy(camera.matrixWorld);
this.originalCameraProjectionMatrix.copy(camera.projectionMatrix);
camera.matrixAutoUpdate = false;
camera.matrixWorld.copy(viewMat);
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
camera.projectionMatrix.copy(projMat);
this.renderer.render(this.tmpScene, camera);
camera.matrixAutoUpdate = true;
camera.matrixWorld.copy(this.originalCameraMatrixWorld);
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
camera.projectionMatrix.copy(this.originalCameraProjectionMatrix);
}
calculateVirtualCamera(portal,portal2,camera,virtualCamera) {
// Calculate player's position and rotation relative to portal1
const relativePosition = this.worldToLocal(portal, camera.position);
// Reflect position and rotate relative to portal2
relativePosition.x = -relativePosition.x;
relativePosition.z = -relativePosition.z;
const relativeRot = new THREE.Quaternion();
relativeRot.copy(portal.quaternion).invert().multiply(camera.quaternion);
const euler = new THREE.Euler(0, Math.PI, 0); // Euler angles in radians (0, 180, 0 degrees)
const quaternion = new THREE.Quaternion().setFromEuler(euler);
relativeRot.multiplyQuaternions(quaternion, relativeRot);
virtualCamera.quaternion.copy(portal2.quaternion).multiply(relativeRot);
// Convert from portal2 local space back to world space
const newPosition = this.localToWorld(portal2, relativePosition);
// Update secondary camera
virtualCamera.position.copy(newPosition);
}
render(camera, recursionLevel = 0, virtualCamera, portal, portal2, viewMat, projMat) {
const gl = this.renderer.getContext();
gl.colorMask(false, false, false, false);
gl.depthMask(false);
gl.disable(gl.DEPTH_TEST);
gl.enable(gl.STENCIL_TEST);
gl.stencilFunc(gl.NOTEQUAL, recursionLevel, 0xff);
gl.stencilOp(gl.INCR, gl.KEEP, gl.KEEP);
gl.stencilMask(0xff);
this.renderScene(camera, [portal, camera], viewMat, projMat);
this.calculateVirtualCamera(portal,portal2,camera,virtualCamera)
const destViewMat = this.computePortalViewMatrix(portal,portal2, viewMat).clone();
const destProjMat = this.computePortalProjectionMatrix(portal2, destViewMat, projMat).clone();
if (recursionLevel == this.maxRecursion) {
gl.colorMask(true, true, true, true);
gl.depthMask(true);
this.renderer.clear(false, true, false);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.STENCIL_TEST);
gl.stencilMask(0x00);
gl.stencilFunc(gl.EQUAL, recursionLevel + 1, 0xff);
portal.visible = false
portal2.visible = false
this.renderScene(virtualCamera, this.scene.children, destViewMat, destProjMat);
portal.visible = true
portal2.visible = true
} else {
this.render(camera, recursionLevel + 1, virtualCamera, portal, portal2, destViewMat, destProjMat);
}
gl.colorMask(false, false, false, false);
gl.depthMask(false);
gl.enable(gl.STENCIL_TEST);
gl.stencilMask(0xff);
gl.stencilFunc(gl.NOTEQUAL, recursionLevel + 1, 0xFF);
gl.stencilOp(gl.DECR, gl.KEEP, gl.KEEP);
gl.disable(gl.STENCIL_TEST);
gl.stencilMask(0x00);
gl.colorMask(false, false, false, false);
gl.enable(gl.DEPTH_TEST);
gl.depthMask(true);
gl.depthFunc(gl.ALWAYS);
this.renderer.clear(false, true, false);
this.renderScene(camera, [portal,portal2, camera], viewMat, projMat);
gl.depthFunc(gl.LESS);
gl.enable(gl.STENCIL_TEST);
gl.stencilMask(0x00);
gl.stencilFunc(gl.LEQUAL, recursionLevel, 0xff);
gl.colorMask(true, true, true, true);
gl.depthMask(true);
gl.enable(gl.DEPTH_TEST);
this.originalCameraMatrixWorld.copy(camera.matrixWorld);
this.originalCameraProjectionMatrix.copy(camera.projectionMatrix);
camera.matrixAutoUpdate = false;
camera.matrixWorld.copy(viewMat);
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
camera.projectionMatrix.copy(projMat);
this.renderScene(camera, this.scene.children, viewMat, projMat);
camera.matrixAutoUpdate = true;
camera.matrixWorld.copy(this.originalCameraMatrixWorld);
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
camera.projectionMatrix.copy(this.originalCameraProjectionMatrix);
}
}
export default class World {
constructor() {
this.game = new Game()
this.scene = this.game.scene
this.physics = new CANNON.World()
this.physics.gravity.set(0, -9.82, 0)
this.physics.broadphase = new CANNON.SAPBroadphase(this.physics)
this.defaultMaterial = new CANNON.Material('default')
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();
this.leftPortal = []
this.rightPortal = []
this.portalPhysics = false
this.collisionDetected = false;
this.virtualCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
this.scene.add(this.virtualCamera);
this.portalHandler = new Portal(this.scene, this.game.renderer.instance);
this.active = ''
this.worldBounds = {
min: new CANNON.Vec3(-100, -100, -100),
max: new CANNON.Vec3(100, 100, 100)
}
const defaultContactMaterial = new CANNON.ContactMaterial(
this.defaultMaterial,
this.defaultMaterial,
{
friction: 1,
restitution: 0
}
)
this.physics.defaultContactMaterial = defaultContactMaterial
window.addEventListener('click', this.shoot.bind(this));
this.setWorld()
this.loadTextures()
this.physics.addEventListener('postStep',()=>{
this.game.controls.cameraBody.position.y - 10
const contacts = this.physics.contacts
let body1,body2
let collide = false
for (let i = 0; i < contacts.length; i++) {
const contact = contacts[i];
body1 = contact.bi;
body2 = contact.bj;
if(body1.class=='camera' && body2.class=='portal'){
collide = true
break
}
}
if(collide && this.leftPortal.length>0 && this.rightPortal.length>0){
if(body2.object && body2.object.physicObject){
body2.object.physicObject.collisionResponse = false
}
}
else{
this.physics.bodies.forEach((body)=>{
if(body.class != 'portal'){
body.collisionResponse = true
this.game.controls.cameraBody.wakeUp()
}
})
}
})
}
worldToLocal(object, vector) {
const worldInverse = new THREE.Matrix4().copy(object.matrixWorld).invert();
return vector.clone().applyMatrix4(worldInverse);
}
localToWorld(object, vector) {
return vector.clone().applyMatrix4(object.matrixWorld);
}
shoot(event) {
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
this.raycaster.setFromCamera({x:0,y:0}, this.game.camera.instance);
const intersects = this.raycaster.intersectObjects([this.plane, this.roof, this.scene.getObjectByName('backWall'), this.scene.getObjectByName('frontWall'), this.scene.getObjectByName('leftWall'), this.scene.getObjectByName('rightWall')]);
if (intersects.length > 0) {
const geometry = new THREE.CircleGeometry(5,32)
const material = new THREE.MeshBasicMaterial( { color: 0x000000 } );
const circle = new THREE.Mesh( geometry, material );
circle.position.x = intersects[0].point.x
circle.position.y = intersects[0].point.y
circle.position.z = intersects[0].point.z
circle.rotation.x = intersects[0].object.rotation.x
circle.rotation.y = intersects[0].object.rotation.y
circle.rotation.z = intersects[0].object.rotation.z
circle.scale.y = 1.5
if(intersects[0].object == this.plane){
circle.position.y += 0.01
}
if(intersects[0].object == this.roof){
circle.position.y -= 0.01
}
if(intersects[0].object == this.scene.getObjectByName('backWall')){
circle.position.z -= 0.01
}
if(intersects[0].object == this.scene.getObjectByName('frontWall')){
circle.position.z += 0.01
this.object = this.physics.bodies[2]
console.log(this.object)
}
if(intersects[0].object == this.scene.getObjectByName('rightWall')){
circle.position.x -= 0.01
}
if(intersects[0].object == this.scene.getObjectByName('leftWall')){
circle.position.x += 0.01
}
if (event.button === 0) {
this.box = new THREE.Mesh(new THREE.BoxGeometry(10,10,10),new THREE.MeshBasicMaterial({color:'red',wireframe:true}))
this.box.scale.y = 1.5
this.box.position.copy(circle.position)
circle.box = this.box
this.boxBody = new CANNON.Body()
this.boxBody.mass = 0
this.boxBody.material = this.defaultMaterial
// this.boxBody.collisionFilterGroup = 1
// this.boxBody.collisionFilterMask = 0
this.boxBody.collisionResponse = false
this.boxBody.addShape(new CANNON.Box(new CANNON.Vec3(5,7.5,5)))
this.boxBody.quaternion.copy(circle.quaternion)
this.boxBody.position.copy(circle.position)
this.boxBody.addEventListener('collide',(event) => {
if(event.body.class == 'camera'){
this.active = 'leftportal'
}
})
this.boxBody.class = 'portal'
this.boxBody.object = intersects[0].object
this.physics.addBody(this.boxBody)
circle.physicObject = this.boxBody
this.leftPortal.push(circle)
} else if (event.button === 2) {
this.box = new THREE.Mesh(new THREE.BoxGeometry(10,10,10),new THREE.MeshBasicMaterial({color:'blue',wireframe:true}))
this.box.scale.y = 1.5
this.box.position.copy(circle.position)
circle.box = this.box
this.boxBody = new CANNON.Body()
this.boxBody.mass = 0
this.boxBody.material = this.defaultMaterial
// this.boxBody.collisionFilterGroup = 1
// this.boxBody.collisionFilterMask = 0
this.boxBody.collisionResponse = false
this.boxBody.addShape(new CANNON.Box(new CANNON.Vec3(5,7.5,5)))
this.boxBody.quaternion.copy(circle.quaternion)
this.boxBody.position.copy(circle.position)
this.boxBody.addEventListener('collide',(event) => {
if(event.body.class == 'camera'){
this.active = 'rightportal'
}
})
this.boxBody.class = 'portal'
this.boxBody.object = intersects[0].object
this.physics.addBody(this.boxBody)
circle.physicObject = this.boxBody
this.rightPortal.push(circle)
}
}
}
calculateAngle(v1, v2) {
const dot = v1.dot(v2);
const angle = Math.acos(dot / (v1.length() * v2.length()));
return angle;
}
isBodyOutOfBounds(body) {
const position = body.position;
return (
position.x < this.worldBounds.min.x ||
position.x > this.worldBounds.max.x ||
position.y < this.worldBounds.min.y ||
position.y > this.worldBounds.max.y ||
position.z < this.worldBounds.min.z ||
position.z > this.worldBounds.max.z
);
}
setWorld() {
const wallShape = new CANNON.Box(new CANNON.Vec3(60, 30, 1))
this.plane = new THREE.Mesh(
new THREE.PlaneGeometry(60, 60),
new THREE.MeshStandardMaterial({
roughness: 0.2,
metalness: 0.1
})
)
this.plane.rotation.x = -Math.PI / 2
this.plane.receiveShadow = true
this.scene.add(this.plane)
const floorShape = new CANNON.Plane()
const floorBody = new CANNON.Body()
floorBody.mass = 0
floorBody.addShape(floorShape)
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(- 1, 0, 0), Math.PI * 0.5)
this.physics.addBody(floorBody)
this.plane.physicObject = floorBody
let backWallMesh = new THREE.Mesh(
new THREE.PlaneGeometry(60, 30),
new THREE.MeshStandardMaterial({
color: 0xf0ffff,
side: THREE.DoubleSide
})
)
backWallMesh.position.z = 30
backWallMesh.position.y = 15
backWallMesh.rotation.y = Math.PI
this.scene.add(backWallMesh)
const backWallBody = new CANNON.Body()
backWallBody.mass = 0
backWallBody.material = this.defaultMaterial
backWallBody.addShape(wallShape)
backWallBody.quaternion.copy(backWallMesh.quaternion)
backWallBody.position.copy(backWallMesh.position)
this.physics.addBody(backWallBody)
backWallMesh.physicObject = backWallBody
const frontWallBody = new CANNON.Body()
frontWallBody.mass = 0
frontWallBody.addShape(wallShape)
frontWallBody.quaternion.copy(backWallMesh.quaternion)
frontWallBody.position.set(0, 15, -30)
this.physics.addBody(frontWallBody)
let frontWallMesh = new THREE.Mesh(
new THREE.PlaneGeometry(60, 30),
new THREE.MeshStandardMaterial({
color: 0xff0fff,
side: THREE.DoubleSide
})
)
frontWallMesh.position.z = -30
frontWallMesh.position.y = 15
frontWallMesh.physicObject = frontWallBody
this.scene.add(frontWallMesh)
let leftWallMesh = new THREE.Mesh(
new THREE.PlaneGeometry(60, 30),
new THREE.MeshStandardMaterial({
color: 0xfff0ff,
side: THREE.DoubleSide
})
)
leftWallMesh.rotation.y = Math.PI / 2
leftWallMesh.position.x = -30
leftWallMesh.position.y = 15
this.scene.add(leftWallMesh)
const leftWallBody = new CANNON.Body()
leftWallBody.mass = 0
leftWallBody.addShape(wallShape)
leftWallBody.quaternion.copy(leftWallMesh.quaternion)
leftWallBody.position.copy(leftWallMesh.position)
this.physics.addBody(leftWallBody)
leftWallMesh.physicObject = leftWallBody
let rightWallMesh = new THREE.Mesh(
new THREE.PlaneGeometry(60, 30),
new THREE.MeshStandardMaterial({
color: 0xffff0f,
side: THREE.DoubleSide
})
)
rightWallMesh.rotation.y = -Math.PI / 2
rightWallMesh.position.x = 30
rightWallMesh.position.y = 15
this.scene.add(rightWallMesh)
const rightWallBody = new CANNON.Body()
rightWallBody.mass = 0
rightWallBody.addShape(wallShape)
rightWallBody.quaternion.copy(rightWallMesh.quaternion)
rightWallBody.position.copy(rightWallMesh.position)
this.physics.addBody(rightWallBody)
rightWallMesh.physicObject = rightWallBody
this.roof = new THREE.Mesh(
new THREE.PlaneGeometry(60, 60),
new THREE.MeshStandardMaterial()
)
this.roof.rotation.x = Math.PI / 2
this.roof.position.y = 30
this.scene.add(this.roof)
backWallMesh.name = 'backWall';
frontWallMesh.name = 'frontWall';
leftWallMesh.name = 'leftWall';
rightWallMesh.name = 'rightWall';
}
loadTextures() {
this.game.resources.on('ready', () => {
})
}
renderPortal(){
this.game.renderer.instance.clear();
this.game.camera.instance.updateMatrixWorld(true);
this.portalHandler.render(this.game.camera.instance, 0, this.virtualCamera, this.rightPortal[0], this.leftPortal[0],this.game.camera.instance.matrixWorld.clone(),this.game.camera.instance.projectionMatrix.clone());
}
update() {
this.physics.step(1 / 60, this.game.time.delta, 3)
if (this.isBodyOutOfBounds(this.game.controls.cameraBody)) {
this.game.controls.cameraBody.sleep()
this.game.controls.cameraBody.position.set(20, 10, 0)
this.game.controls.cameraBody.wakeUp()
}
if (this.rightPortal.length > 0 && this.leftPortal.length > 0){
this.renderPortal()
}
else{
this.game.renderer.instance.render(this.scene, this.game.camera.instance)
}
this.managePortals()
if (this.rightPortal.length > 0 && this.leftPortal.length > 0) {
this.checkLeftPortalTeleport()
this.checkRightPortalTeleport()
} else {
this.resetPortalColors()
}
}
managePortals() {
// Manage left portals
this.leftPortal.forEach((portalData) => {
this.scene.add(portalData)
})
if (this.leftPortal.length > 1) {
const circleToRemove = this.leftPortal.shift()
this.scene.remove(circleToRemove.box)
this.scene.remove(circleToRemove.torus)
this.physics.remove(circleToRemove.physicObject)
this.scene.remove(circleToRemove)
}
// Manage right portals
this.rightPortal.forEach((portalData) => {
this.scene.add(portalData)
})
if (this.rightPortal.length > 1) {
const circleToRemove = this.rightPortal.shift()
this.scene.remove(circleToRemove.box)
this.scene.remove(circleToRemove.torus)
this.physics.remove(circleToRemove.physicObject)
this.scene.remove(circleToRemove)
}
}
teleport(portal, portal2, camera, body) {
// Calculate player's position and rotation relative to portal1
const relativePosition = this.worldToLocal(portal, camera.position);
// Reflect position and rotate relative to portal2
relativePosition.x = -relativePosition.x;
relativePosition.z = -relativePosition.z;
// Convert Cannon.js quaternion to Three.js quaternion
const bodyQuat = new THREE.Quaternion(body.quaternion.x, body.quaternion.y, body.quaternion.z, body.quaternion.w);
const relativeRot = new THREE.Quaternion();
relativeRot.copy(portal.quaternion).invert().multiply(camera.quaternion);
const euler = new THREE.Euler(0, Math.PI, 0); // Euler angles in radians (0, 180, 0 degrees)
const quaternion = new THREE.Quaternion().setFromEuler(euler);
relativeRot.multiplyQuaternions(quaternion, relativeRot);
// Apply the transformation to the body quaternion
const newBodyQuat = new THREE.Quaternion();
newBodyQuat.copy(portal2.quaternion).multiply(relativeRot);
// Convert the resulting quaternion back to Cannon.js format
body.quaternion.set(newBodyQuat.x, newBodyQuat.y, newBodyQuat.z, newBodyQuat.w);
// Convert from portal2 local space back to world space
const newPosition = this.localToWorld(portal2, relativePosition);
body.position.copy(newPosition);
camera.quaternion.copy(newBodyQuat)
}
checkLeftPortalTeleport() {
const portalForward = new THREE.Vector3()
this.rightPortal[0].getWorldDirection(portalForward)
const travelerPosition = this.game.camera.instance.position.clone()
const portalPosition = this.rightPortal[0].position.clone()
const portalToTraveler = travelerPosition.sub(portalPosition)
const dotProduct = portalForward.dot(portalToTraveler)
if (dotProduct < 0) {
this.teleport(this.rightPortal[0],this.leftPortal[0],this.game.camera.instance,this.game.controls.cameraBody)
}
}
checkRightPortalTeleport() {
const portalForward = new THREE.Vector3()
this.leftPortal[0].getWorldDirection(portalForward)
const travelerPosition = this.game.camera.instance.position.clone()
const portalPosition = this.leftPortal[0].position.clone()
const portalToTraveler = travelerPosition.sub(portalPosition)
const dotProduct = portalForward.dot(portalToTraveler)
if (dotProduct < 0) {
this.teleport(this.leftPortal[0],this.rightPortal[0],this.game.camera.instance,this.game.controls.cameraBody)
}
}
resetPortalColors() {
if (this.leftPortal.length > 0) {
this.leftPortal[0].material.color = new THREE.Color(0xff9a00)
this.leftPortal[0].material.needsUpdate = true
}
if (this.rightPortal.length > 0) {
this.rightPortal[0].material.color = new THREE.Color(0x00a2ff)
this.rightPortal[0].material.needsUpdate = true
}
}
}
``'