Skip to content

Commit

Permalink
ThreeGraphics - Smoother camera follows
Browse files Browse the repository at this point in the history
`ThreeGraphics` - Adds 2d scrolling of UVS as box style option
`ThreeGraphics` - Adds ability to render 3d text
`ThreeGraphics` - Improved 2d platform side-scrolling camera
  • Loading branch information
Marak committed Mar 13, 2024
1 parent 56eb8f0 commit 8b9f760
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 162 deletions.
2 changes: 1 addition & 1 deletion mantra-client/public/markup.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
'UnitSpawner'
],
showLoadingScreen: false,
gameRoot: 'http://192.168.1.80:7777/'

});
window.game = game;
game.start(function () {
Expand Down
4 changes: 2 additions & 2 deletions mantra-client/public/maze.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
// game.use('Bullet');
game.use('Sutra');
game.use('Gamepad');
game.use('GamepadGUI');
// game.use('GamepadGUI');
game.start(function () {
game.use('Editor');
// game.use('Editor');
game.use(new WORLDS.worlds['Maze']());
});
window.game = game;
Expand Down
10 changes: 2 additions & 8 deletions mantra-client/public/music.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,10 @@
collisions: true,
graphics: ['css'], // array enum, 'babylon', 'css', 'three'
camera: 'follow',
options: {
scriptRoot: '.',
assetRoot: '.'
}
plugins: ['Gamepad'],
gameRoot: '.'
});

// game.use('Bullet');
game.use('Sutra');
game.use('Gamepad');
game.use('GamepadGUI');
game.start(function () {
game.use('Editor');
game.use(new WORLDS.worlds['Music']());
Expand Down
3 changes: 2 additions & 1 deletion mantra-client/public/tiled.html
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@
if (graphics === 'three') {
//game.setZoom();
}

game.reset();
game.config.useFoV = true;
if (graphics === 'css') {
game.setZoom(4.5);
}
Expand Down
4 changes: 2 additions & 2 deletions mantra-game/Game.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,9 +529,10 @@ class Game {
}
}

reset(mode, clearSutra = true) {
reset(mode = 'topdown', clearSutra = true) {

let game = this;
game.data.mode = mode;

// reset all Sutra rules
if (clearSutra) {
Expand All @@ -558,7 +559,6 @@ class Game {

// reset the default player controls
this.setControls({});

this.setCameraMode('follow');

// set the default movement sutra
Expand Down
67 changes: 57 additions & 10 deletions mantra-game/plugins/graphics-three/ThreeGraphics.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// ThreeGraphics.js - Marak Squires 2023
import { MOUSE, Scene, WebGLRenderer, PerspectiveCamera, HemisphereLight, Vector3 } from 'three';
import { OrbitControls } from '../../vendor/three/examples/jsm/controls/OrbitControls.js';
import { FontLoader } from '../../vendor/three/examples/jsm/loaders/FontLoader.js';

import GraphicsInterface from '../../lib/GraphicsInterface.js';

Expand All @@ -10,6 +11,7 @@ import updateGraphic from './lib/updateGraphic.js';
import removeGraphic from './lib/removeGraphic.js';
import inflateGraphic from './lib/inflateGraphic.js';
import inflateTexture from './lib/inflateTexture.js';
import inflateText from './lib/inflateText.js';

// import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
// import { FontLoader } from 'three/addons/loaders/FontLoader.js';
Expand Down Expand Up @@ -53,15 +55,18 @@ class ThreeGraphics extends GraphicsInterface {
this.updateGraphic = updateGraphic.bind(this);
this.removeGraphic = removeGraphic.bind(this);
this.inflateTexture = inflateTexture.bind(this);
this.inflateText = inflateText.bind(this);
this.game = game;
this.game.systemsManager.addSystem('graphics-three', this);

if (typeof this.game.threeReady === 'undefined') {
this.game.threeReady = false; // TODO: remove this, check case in SystemManager for double plugin init
}

this.loadFont(()=>{
this.threeReady(game);
});

this.threeReady(game);
}

threeReady(game) {
Expand Down Expand Up @@ -143,7 +148,6 @@ class ThreeGraphics extends GraphicsInterface {
window.addEventListener('resize', this.onWindowResize.bind(this), false);

document.body.style.cursor = 'default';

}

setCameraDefaults() {
Expand All @@ -165,12 +169,12 @@ class ThreeGraphics extends GraphicsInterface {
this.renderer.setSize(window.innerWidth, window.innerHeight);
}

loadFont(path, cb) {
loadFont(cb) {
let game = this.game;
const fontLoader = new FontLoader();
fontLoader.load('vendor/fonts/helvetiker_regular.typeface.json', function (font) {
// Store the loaded font in your game's state
console.log('got back', null, font)
// console.log('got back', null, font)
game.font = font;
cb(null, font)
//game.setFont(font);
Expand All @@ -186,7 +190,10 @@ class ThreeGraphics extends GraphicsInterface {
updateCamera() {
let game = this.game;

// if (this.isManualControlActive) return; // Skip automatic following if manual control is active
/* default camera view
this.camera.position.set(0, 400, 100);
this.camera.lookAt(new Vector3(0, 150, -100));
*/

// Get the current player entity
const currentPlayer = game.getEntity(game.currentPlayerId);
Expand All @@ -210,27 +217,67 @@ class ThreeGraphics extends GraphicsInterface {
this.setFirstPersonView(playerGraphic);
break;
case 'follow':
case 'platform': // 'follow' and 'platform' share the same logic
this.updateFollowCamera(playerGraphic);
break;
case 'platform': // 'follow' and 'platform' share the same logic
this.updatePlatformCamera(playerGraphic);
break;
default:
console.warn('Unknown camera mode:', game.data.camera.mode);
}
}

updateFollowCamera(playerGraphic) {
updatePlatformCamera(playerGraphic) {
// Calculate the new camera position with an offset
const offset = new Vector3(0, 150, -100); // Adjust the offset as needed
const newPosition = playerGraphic.position.clone().add(offset);
const lookAtPosition = playerGraphic.position.clone();



// Update orientation only when not manually controlling
if (!this.isManualControlActive) {
// Smoothing the camera movement
this.smoothCameraUpdate(newPosition, lookAtPosition);
this.camera.position.copy(newPosition);
this.camera.lookAt(lookAtPosition);
}
}

updateFollowCamera(playerGraphic) {
// Calculate the new camera position with an offset

// top-down
let offset;

if (this.game.data.mode === 'topdown') {
offset = new Vector3(0, 150, -100); // Adjust the offset as needed
let newPosition = playerGraphic.position.clone().add(offset);
// top-down
let lookAtPosition = playerGraphic.position.clone();

// Update orientation only when not manually controlling
if (!this.isManualControlActive) {
this.camera.position.copy(newPosition);
this.camera.lookAt(lookAtPosition);
this.camera.up.set(0, 1, 0);
}

} else if (this.game.data.mode === 'platform') {

offset = new Vector3(0, 250, 0);

let newPosition = playerGraphic.position.clone().add(offset);
// 2d platform side view
let lookAtPosition = new Vector3(playerGraphic.position.x, playerGraphic.position.y, playerGraphic.position.z);

// Update orientation only when not manually controlling
if (!this.isManualControlActive) {
this.camera.position.copy(newPosition);
this.camera.lookAt(lookAtPosition);

this.camera.up.set(0, -1, 0);
}

}


}

Expand Down
12 changes: 11 additions & 1 deletion mantra-game/plugins/graphics-three/lib/createGraphic.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ export default function createGraphic(entityData) {
//console.log('entityObject after processModel()', entityObject);
} else {
// console.log("Creating new geometry for entity.");
entityObject = createGeometryForEntity(entityData);



entityObject = createGeometryForEntity.call(this, entityData);
}

// Set entity position and add to the scene
Expand Down Expand Up @@ -71,6 +74,13 @@ function createGeometryForEntity(entityData) {
let geometry, material, mesh;
const entityGroup = new Group();

if (entityData.type === 'TEXT') {
mesh = this.inflateText(entityData, this.scene, this.game.font);
entityGroup.add(mesh);

return entityGroup;
}

// Define geometry based on entity type
switch (entityData.type) {
case 'BORDER':
Expand Down
60 changes: 60 additions & 0 deletions mantra-game/plugins/graphics-three/lib/inflateText.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { TextGeometry } from '../../../vendor/three/examples/jsm/geometries/TextGeometry.js';
import { Vector3, MeshPhongMaterial, Mesh, Color } from 'three';

export default function inflateText(entityData, scene, font) {
let text = entityData.text;
let style = entityData.style || {}; // Ensure style exists to prevent errors

// Default values
let fontSize = 80; // Default font size
let fontColor = 0xff0000; // Default font color (red)

// Override default values if specific styles are provided
if (style.fontSize) {
fontSize = parseInt(style.fontSize, 10); // Convert fontSize from CSS style to integer
}
if (style.color) {
fontColor = new Color(style.color).getHex(); // Convert CSS color string to Three.js color hex
}

let textGeo = new TextGeometry(text, {
font: font,
size: fontSize, // Use the fontSize from style or default
height: 1, // Thickness of the text. Adjust as needed.
curveSegments: 12, // How many curve segments to use for letters, more makes letters rounder.
bevelEnabled: true, // Whether to use bevel edges.
bevelThickness: 2, // How deep the bevel is.
bevelSize: 1.5, // How far the bevel extends from the text outline.
bevelOffset: 0,
bevelSegments: 5 // Number of bevel segments.
});

let textMaterial = new MeshPhongMaterial({
color: fontColor, // Use the fontColor from style or default
specular: 0xffffff // Specular color of the material (for shininess). Adjust as needed.
});

let mesh = new Mesh(textGeo, textMaterial);

mesh.rotation.y = Math.PI; // Math.PI radians equals 180 degrees

// Compute the geometry's bounding box to get its dimensions
textGeo.computeBoundingBox();
const boundingBox = textGeo.boundingBox;

// Calculate the offset to center the text
const offset = new Vector3();
offset.x = (boundingBox.max.x - boundingBox.min.x) / 2; // Center in X
offset.y = boundingBox.min.y; // Align bottom to origin in Y

// Move the mesh by the calculated offset
mesh.geometry.translate(-offset.x, -offset.y, 0);


// Add the mesh to the scene if provided
if (scene) {
scene.add(mesh);
}

return mesh; // Return the text mesh in case you need to further manipulate it.
}
65 changes: 60 additions & 5 deletions mantra-game/plugins/graphics-three/lib/inflateTexture.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TextureLoader } from 'three';
import { TextureLoader, RepeatWrapping } from 'three';

export default async function inflateTexture(entityData) {
if (!entityData.texture) return;
Expand Down Expand Up @@ -52,12 +52,30 @@ async function applyTextureToEntityGraphic(game, entityData, entityGraphic) {
// Traverse the entity graphic component and apply texture to all child meshes
entityGraphic.traverse((child) => {
if (child.isMesh) {
applyTextureToMesh(child, cachedTexture, texture.sprite);
applyTextureToMesh(child, cachedTexture, texture.sprite, entityData);
}
});
}

function applyTextureToMesh(mesh, cachedTexture, sprite) {
function flipUVsHorizontally(geometry) {
const uvs = geometry.attributes.uv;
for (let i = 0; i < uvs.array.length; i += 2) {
uvs.array[i] = 1 - uvs.array[i]; // Flip the U coordinate
// flip the V coordinate
uvs.array[i + 1] = 1 - uvs.array[i + 1];

// At this stage, top side looks GOOD
// if we uncomment the following line, the sides look good, the TOP is upside down
uvs.array[i + 1] = 1 - uvs.array[i + 1];
// Remark: how to now flip the top side?
uvs.array[i + 1] = 1 - uvs.array[i + 1];


}
uvs.needsUpdate = true; // Inform Three.js that the UVs have been updated
}

function applyTextureToMesh(mesh, cachedTexture, sprite, entityData) {
// console.log("applyTextureToMesh", mesh, cachedTexture, sprite)
if (sprite) {
// Adjust UV mapping for sprites
Expand All @@ -73,16 +91,53 @@ function applyTextureToMesh(mesh, cachedTexture, sprite) {
mesh.material.map = clonedTexture;
} else {
// For non-sprite textures that need to be flipped on the X-axis


let clonedTexture = cachedTexture.clone();
clonedTexture.repeat.set(-1, 1); // Flip texture on the X-axis
clonedTexture.offset.set(1, 0); // Adjust offset for the flipped texture

// TODO: entityData.texture.scrollingRepeat
clonedTexture.wrapS = clonedTexture.wrapT = RepeatWrapping; // Set texture to repeat

//clonedTexture.repeat.set(-1, 1);
//clonedTexture.offset.set(1, 0);
// Remark: Ths is now working; however it seems to be toggling state back and forth each game tick? that is not expected
// we should be able to set this once and be done with it

if (!mesh.userData.uvsFlipped) {
flipUVsHorizontally(mesh.geometry);
mesh.userData.uvsFlipped = true; // Mark UVs as flipped
}
// mesh.rotation.y += 0.01
// TODO: entityData.texture.scrollingRepeat
// console.log(sprite)
if (entityData && entityData.style && entityData.style.scrollTexture) {
scrollTexture(mesh, 0.000, 0.001);
}


mesh.material.map = clonedTexture;
}
mesh.material.needsUpdate = true;
mesh.visible = true;
}

function scrollTexture(mesh, speedX = 0.01, speedY = 0.01) {
const geometry = mesh.geometry;

// Assuming the geometry is using BufferGeometry
const uvs = geometry.attributes.uv;

// Update UV coordinates for scrolling effect
for (let i = 0; i < uvs.array.length; i += 2) {
uvs.array[i] += speedX; // Scroll horizontally
uvs.array[i + 1] += speedY; // Scroll vertically
}

// Mark the UV attribute as needing an update in the next render cycle
uvs.needsUpdate = true;
}


function calculateSpriteUVs(sprite, textureWidth, textureHeight) {
sprite.width = sprite.width || 16; // Default sprite width
sprite.height = sprite.height || 16; // Default sprite height
Expand Down
Loading

0 comments on commit 8b9f760

Please sign in to comment.