Sky Lantern the Wish is a 3D Interactive Web application. Our idea comes from the traditional Chinese culture of making "Kongming lantern" where people hand-writing their wishes on a small hot air balloon that made of paper and set it up to the sky at night. In Asia and elsewhere around the world, sky lanterns have been traditionally made for centuries, to be launched for play or as part of long-established festivities.
The Sky Lantern represents the holding of beautiful wishes; we want to inherit the beautiful culture and make it into the digital world that people can assess and making sky lanterns, wishes on the internet.
When people go inside our application, they will see an ocean and a sky full of lanterns. After clicking the "make a wish" button, users can type their wishes that will show on the "lantern", then they can release it up to the "sky" and restore it on the "sky". By moving the mouse and pressing the up, down, left, right keys, the user will be able to change their views and "fly" into the "sky", to see other's wishes as well.
Our user flow is:
1. open the website and see the opening lines
2. click the make a wish button and type some wishes to the lantern
3. press the return(enter) key for releasing the lantern to the lanterns sky
4. move the mouse and press the up, down, left, right keys to change the view and "fly" to the sky to see other's wishes
5. make another wish - then back to 2,3,4 flow
Here will be the full experience for having and releasing a wish lantern to the sky.
What We Made:
Three.js
Using Three.js to build our world, where users have controls over their position and perspectives. (First person control)
A waving surface of an ocean/sea/whatever (GeoPlane)
3D models with user inputs as texture
Animation and transition of objects and camera (Tween.js)
DOM
Layouts of the webpage with CSS, tried to use ‘rem’ as the unit to make the site responsive on mobile devices.
Simple animations of the displayed page with CSS transition and Javascript
Having a canvas as the texture, to map it to the right position, I have to use ‘text wrap’ to restrain it
The transition between elements (pages)
3D
Figured out how a model is being rendered in a computer and the relationship between all the elements ( after years, finally )
Draw the UV maps and the textures as part of the material.
Modify the 3D model we purchased online, to get the render result faster and better in WebGL
Others
We try to have some fancy ‘poem-like’ lines about wishes, as we both are not native speakers, we use AI generated contents in the beginning…
–
I firstly started to study the Three.js and WebGL basics for a week, and tried to understand the logic and how to use it. Because I know 3D softwares a little bit, so I quickly understood the 3D everionment and how the camera the OBJ. files looks like.
I was in charge of making the all the 3D animations by code, it a complicated process to deal with cameras and controllers settings, and I used Tween.js as well. A big challenge for me is to control both the user input with the key board and the view changes by mouse moving and keys press, also for the reset camera part. Because I am using the first person control function to control the view of our web evrionment, it has the conflict between the control object and the mouse's position. So I changed the library of it a little bit for my reset camera and mouse position's purpose. below is the changes:
Xinyue built the fundamental site interactions with Javascript, HTML, and CSS, and figured out the part where user inputs come to the lantern texture in Three.js.
Other Resources & libraries: Tween.js; Hover.css; Poem Generator (https://www.poem-generator.org.uk/quick/)
There are tons of tutorials online showing how the change the texture, but they’re all demonstrated in a cube shape and repeated patterns, not many things about a customized mesh imported from an OBJ file. So I started with 3D software then moved to the code. Below’s my attempt to do the mapping –
Coding Stages
1 stage
building the environmen
we chose using the THREE.JS for building 3D environment, so we tried planeGeometry for making an "ocean".
var planeGeo = new THREE.PlaneGeometry(planeSize, planeSize, planeDefinition, planeDefinition);
var plane = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({
color: meshColor,
transparent: true ,
}));
var plane = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({
color: meshColor,
transparent: true ,
}));
for making the still ocean wave, we are updating the numbers in the vertical function
for (var i = 0; i < planeGeo.vertices.length; i++) {
var z = +planeGeo.vertices[i].z;
planeGeo.vertices[i].z = Math.sin(( i + count * 0.00002)) * (planeGeo.vertices[i]._myZ - (planeGeo.vertices[i]._myZ* 0.6))
plane.geometry.verticesNeedUpdate = true;
count += 0.1
}
var z = +planeGeo.vertices[i].z;
planeGeo.vertices[i].z = Math.sin(( i + count * 0.00002)) * (planeGeo.vertices[i]._myZ - (planeGeo.vertices[i]._myZ* 0.6))
plane.geometry.verticesNeedUpdate = true;
count += 0.1
}
also the background
background = "#002134",
Stage 2
building the lanterns
we are using load MTL and load OBJ files for adding 3D lanterns on the environment
function loadModelSky(){
var onProgress = function ( xhr ) {
if ( xhr.lengthComputable ) {
var percentComplete = xhr.loaded / xhr.total * 100;
}
};
var onError = function () { };
new THREE.MTLLoader()
.load( 'assets/lowpolygroup.mtl', function ( materials ) {
materials.preload();
new THREE.OBJLoader()
.setMaterials( materials )
.load( 'assets/lowpolygroup.obj', function ( object ) {
object.position.set(120000,60000,10000);
object.scale.set(50,50,50);
scene.add( object );
}, onProgress, onError );
} );
var onProgress = function ( xhr ) {
if ( xhr.lengthComputable ) {
var percentComplete = xhr.loaded / xhr.total * 100;
}
};
var onError = function () { };
new THREE.MTLLoader()
.load( 'assets/lowpolygroup.mtl', function ( materials ) {
materials.preload();
new THREE.OBJLoader()
.setMaterials( materials )
.load( 'assets/lowpolygroup.obj', function ( object ) {
object.position.set(120000,60000,10000);
object.scale.set(50,50,50);
scene.add( object );
}, onProgress, onError );
} );
}
for the texture is a really hard one to code...
we want to not only make it a .jpg texture, but also make it can be applied text onto the texture...
function generateTexture(){
wishCanvas.width = 4096;
wishCanvas.height = 4096;
wishContext.fillStyle = 'black';
wishContext.font = "96px Amatic";
wishContext.drawImage(baseTex,0,0);
texture = new THREE.CanvasTexture(wishCanvas);
texture.needsUpdate = true;
wrapText(wishContext,inputText,2330,2050,maxWidth,lineHeight);
}
function wrapText(wishContext, text, x, y, maxWidth, lineHeight) {
var words = text.split(' ');
var line = '';
wishCanvas.width = 4096;
wishCanvas.height = 4096;
wishContext.fillStyle = 'black';
wishContext.font = "96px Amatic";
wishContext.drawImage(baseTex,0,0);
texture = new THREE.CanvasTexture(wishCanvas);
texture.needsUpdate = true;
wrapText(wishContext,inputText,2330,2050,maxWidth,lineHeight);
}
function wrapText(wishContext, text, x, y, maxWidth, lineHeight) {
var words = text.split(' ');
var line = '';
for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = wishContext.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
wishContext.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
}
else {
line = testLine;
}
}
wishContext.fillText(line, x, y);
}
var testLine = line + words[n] + ' ';
var metrics = wishContext.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
wishContext.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
}
else {
line = testLine;
}
}
wishContext.fillText(line, x, y);
}
and put it into the animate() function, which is updating the text that the input of users.
Stage 3
set up controllers
We are using the perspective camera with THREE.JS, and setting up a FirstPersonControl controller for moving and changing the view perspective and positions.
We made changes in the FirstPersonControls library because it cannot be used as we want.
var controls = new THREE.FirstPersonControls( camera );
and a resetCamera function for resetting views when enter another wish.
function resetCamera(){
camera.position.x = -1633;
camera.position.z = 17000;
camera.position.y = 23300;
// camera.lookAt(-1533,23000,17000);
// controls.object.position = new THREE.Vector3 (-1633,17000,23300);
controls.target.x = 7000;
controls.target.y = 23000;
controls.target.z = 17000;
controls.object.position.x = -1633;
controls.object.position.y = 23300;
controls.object.position.z = 17000;
camera.position.x = -1633;
camera.position.z = 17000;
camera.position.y = 23300;
// camera.lookAt(-1533,23000,17000);
// controls.object.position = new THREE.Vector3 (-1633,17000,23300);
controls.target.x = 7000;
controls.target.y = 23000;
controls.target.z = 17000;
controls.object.position.x = -1633;
controls.object.position.y = 23300;
controls.object.position.z = 17000;
controls.lat = 0;
controls.lon = 0;
controls.phi = 0;
controls.theta = 0;
controls.lon = 0;
controls.phi = 0;
controls.theta = 0;
resetCameraTrue = true;
}
}
Stage 4
adding css, html and js (with tween.js) animations.
something like that examples:
//tween kongmingdeng fly animation
var tween;
function initTween() {
var randomX = Math.floor((Math.random() * 700) + 60000);
var randomY = Math.floor((Math.random() * 10000) + 70000);
var randomZ = Math.floor((Math.random() * (-20000)) + 40000);
tween = new TWEEN.Tween(objsize)
.to( { x: randomX,y:randomY,z:randomZ}, 17000 );
tween.start();
tween.easing(TWEEN.Easing.Sinusoidal.InOut);
}
var tween;
function initTween() {
var randomX = Math.floor((Math.random() * 700) + 60000);
var randomY = Math.floor((Math.random() * 10000) + 70000);
var randomZ = Math.floor((Math.random() * (-20000)) + 40000);
tween = new TWEEN.Tween(objsize)
.to( { x: randomX,y:randomY,z:randomZ}, 17000 );
tween.start();
tween.easing(TWEEN.Easing.Sinusoidal.InOut);
}
<div class="bottom" id="bottom-button">
<div id="button-start" onclick="makeWish()" class="hvr-underline-from-left">
<h2> Make A Wish</h2>
</div>
<div id="button-start" onclick="makeWish()" class="hvr-underline-from-left">
<h2> Make A Wish</h2>
</div>
.blkCanvas{
display: none;
width:100%;
height:100%;
position: absolute;
transition: opacity 1s ease-in;
z-index: 40;
opacity: 1;
}
display: none;
width:100%;
height:100%;
position: absolute;
transition: opacity 1s ease-in;
z-index: 40;
opacity: 1;
}
Stage 5
collecting and re-editing the functions and orders of the code. important and a lot of debugging....
And WE MADE IT!!!!!! (include 2 night of not sleeping at all...)
Questions:
Is that clear to make a wish?
How do you want to fly in the sky?
Does it matter to see others' wishes?
Is that comfortable for typing the wishes?
For the future:
1.Set up a server and restore the text(wishes) that the input of users, also load those inputs for automatically adding textures on the lantern.
2. We want to make it a Web VR movie, this will be our first scene (hopefully).
concept:
years and years, we make wishes
years and years after, where our wishes go?
scenes after scenes, we will see the unknow "AI" are transfering our wishes to something and some worlds...
3.also re-editing the style of visuals.