Good Animation
Good One
* resource:
* https://threejsfundamentals.org/threejs/lessons/threejs-fundamentals.html
* https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene
* https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/
* https://sbcode.net/threejs/dat-gui/
* https://github.com/markuslerner/THREE.Interactive
* video:
* https://threejs-journey.xyz/
* example
* image plane fly-through: https://www.oculus.com/medal-of-honor/
* 3d pin ball: http://letsplay.ouigo.com/
* world switch: https://zen.ly/
* environment: https://chartogne-taillet.com/en
* https://www.facebook.com/groups/webtrendcollection
* wave:
* https://1stwebdesigner.com/examples-of-three-js-in-action/
====== Browse Support ======
* some feature using WebGL 2.0 and some old browser or some mobile browser may not support it,
* you can use code to detect webgl2 support and dynamic change the way of coding to support other browser
* webgl2 support browser tabel: https://caniuse.com/webgl2
* check webgl2 support
// check webgl2 support for multi sample version
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) {
alert('your browser/OS/drivers do not support WebGL2');
console.log('your browser/OS/drivers do not support WebGL2');
} else {
console.log('webgl2 works!');
}
====== Tools ======
* node.js (other downloads for zip version): https://nodejs.org/en/download/
* gui js options:
* dat.GUI
* control-panel
* ControlKit
* Guify
* Oui
* https://tinypng.com/
* https://matheowis.github.io/HDRI-to-CubeMap/
* prepare tip:
* use 2x1 ratio panorama with horizontal tile-able texture as base layer
* photoshop > spherical panorama > new panorama layer from selected layer to create a sphere texture
* paint the seam on pole area, (since already fix horizontal seam in 2D)
* once done, spherical panorama > export the result as 2x1 texture, then use above panorama to cubemap tool
* https://gero3.github.io/facetype.js/
* texture
* https://www.poliigon.com/
* https://3dtextures.me/
* https://www.arroway-textures.ch/
* https://polyhaven.com/
* https://github.com/nidorx/matcaps
* https://kenney.nl/assets/particle-pack
====== Three.js Concept ======
* Object3D (position, rotation, scale, quaternion rotation): Mesh, Light, Camera, Group, AxesHelper
* Mesh = Geometry + Material/Shader
* HTML Element Content <=(Result Target) Renderer.render <= Mesh + Light + Camera
**example's js vs jsm**
* jsm : js module, it is for use with three.js as module style,
* like in code "import { OrbitControls } from './path/OrbitControls.js"
* and use its class like "new OrbitControls( main_cam, main_render.domElement );"
* js: the traditional js way,
* load under THREE like ""
* and use its class like "new THREE.OrbitControls( main_cam, main_render.domElement );"
**3D Format for Web**
* GLTF: GL transmission format, support geo/mat/cam/lgt/scene/ani/bone/morph, (json, binary, binary compressed, embed texture)
* gltf_obj.scene.children (array) > [] > cam/mesh/light
====== Common Code ======
* matcap material (fake light material without light)
const brown_mctex = main_texLoader.load('./img/matcap/746761_291C19_AB9385_3C2B27.jpg')
const brown_mat = new THREE.MeshMatcapMaterial({matcap:brown_mctex})
* color material
const white_mat = new THREE.MeshStandardMaterial({
color:'#54c1c8',
roughness: 0.3,
metalness: 0.2,
side: THREE.DoubleSide
})
* helper indicator
// axis
const main_axis = new THREE.AxesHelper()
scene.add(main_axis)
**Events**
* mouse tracking
//# == add event: mouse position tracking
let mouse = {
x: 0,
y: 0
}
window.addEventListener('mousemove', (event)=>{
//console.log(event.clientX) // from top left corner
// top/left to center coordinate
mouse.x = (event.clientX / output_size.width)*2-1
mouse.y = -(event.clientY / output_size.height)*2+1
})
* double click full window
window.addEventListener('dblclick', ()=>{
const full_screen_element = document.fullscreenElement || document.webkitFullscreenElement
//output.requestFullScreen()
if (!full_screen_element){
if (output.requestFullscreen){
// normal browse support
output.requestFullscreen()
} else if (output.webkitRequestFullscreen){
// safari
output.webkitRequestFullscreen()
}
}else{
if (document.exitFullscreen){
// normal browse support
document.exitFullscreen()
} else if (document.webkitExitFullscreen){
// safari
document.webkitExitFullscreen()
}
}
})
* simple key down event
const xSpeed = 0.1;
const ySpeed = 0.1;
window.addEventListener("keydown", onDocumentKeyDown, false);
function onDocumentKeyDown(event) {
const keyCode = event.which;
console.log(keyCode)
if (keyCode == 40) {
boat_obj.position.z += ySpeed;
} else if (keyCode == 38) {
boat_obj.position.z -= ySpeed;
} else if (keyCode == 37) {
boat_obj.position.x -= xSpeed;
} else if (keyCode == 39) {
console.log('forward')
boat_obj.position.x += xSpeed;
}
else if (keyCode == 32) {
boat_obj.position.set(0, 0, 0);
}
};
* extra offset key event for tick()
const offset_step = 0.02
if ( keyboard.pressed("w") ){
main_boat_offset.position.y -= offset_step
console.log(main_boat_offset.position.y)
}
if ( keyboard.pressed("s") ){
main_boat_offset.position.y += offset_step
console.log(main_boat_offset.position.y)
}
if ( keyboard.pressed("e") ){
main_boat_offset.position.z -= offset_step
console.log(main_boat_offset.position.z)
}
if ( keyboard.pressed("q") ){
main_boat_offset.position.z += offset_step
console.log(main_boat_offset.position.z)
}
if ( keyboard.pressed("d") ){
main_boat_offset.position.x -= offset_step
console.log(main_boat_offset.position.x)
}
if ( keyboard.pressed("a") ){
main_boat_offset.position.x += offset_step
console.log(main_boat_offset.position.x)
}
**GUI**
* simple gui control
// -- gui
const gui = new dat.GUI({ closed:true, width: 300})
dat.GUI.toggleHide();
const global_ctrl = {
boat: {
x: 0,
y: 0,
z: 0,
color:0x00ff00,
slide: ()=>{
let tl = gsap.timeline();
const cur_x = green_box_mesh.position.x
tl.to(green_box_mesh.position, {duration: 2, x: cur_x+2})
.to(green_box_mesh.position, {duration: 1, x: cur_x})
},
}
}
gui.add(global_ctrl.boat, 'x', -10, 10, 0.1).name('posX').onChange( ()=> {
boat_obj.position.x = global_ctrl.boat.x
} )
gui.add(global_ctrl.boat, 'y', -10, 10, 0.1).name('posY').onChange( ()=> {
boat_obj.position.y = global_ctrl.boat.y
} )
gui.add(global_ctrl.boat, 'z', -50, 50, 0.1).name('posZ').onChange( ()=> {
boat_obj.position.z = global_ctrl.boat.z
} )
gui.addColor(global_ctrl.boat, 'color').onChange(
()=>{
green_box_mesh.material.color.set(global_ctrl.green_box.color)
}
)
gui.add(global_ctrl.boat, 'slide')
**font**
* text
//-- font
const text_mat = new THREE.MeshMatcapMaterial({matcap:brown_mctex})
let text_mesh = undefined
const main_fontLoader = new THREE.FontLoader(load_manager)
main_fontLoader.load(
'./font/helvetiker_regular.typeface.json',
(result_font)=>{
console.log('font loaded')
const bevel_a = 0.01 //2
const bevel_b = 0.01 //3
const text_geo = new THREE.TextBufferGeometry(
'Drive Through to Next Scene > ',{
font: result_font,
size: 0.2,
height: .05,
curveSegments: 3,
// bevelEnabled: true,
// bevelThickness: bevel_b,
// bevelSize: bevel_a,
// bevelOffset: 0,
// bevelSegments: 2
}
)
text_geo.computeBoundingBox() // max3, min3
text_geo.center()
text_mesh = new THREE.Mesh(text_geo, white_2_mat) // text_mat
text_mesh.position.y = 1
text_mesh.position.x = 123
scene.add(text_mesh)
}
)
====== Three.js Problem and Solution======
* limit orbit pan: https://discourse.threejs.org/t/how-to-limit-pan-in-orbitcontrols-for-orthographiccamera/9061
* limit the maximum view angle on width or basically auto adjust vFOV given hFOV (basically check width and fov dynamically):
// ref: https://discourse.threejs.org/t/responsive-renderer-with-limits/4401/16
renderer.setSize( window.innerWidth, window.innerHeight );
camera.aspect = window.innerWidth / window.innerHeight
const viewAspect = viewWidth / viewHeight
const special = camera.aspect > viewAspect
uniform.value = special ? 1 : 0
if(special){
const camH = Math.tan(THREE.Math.degToRad(myFov/2))
const ratio = camera.aspect / viewAspect
const newH = camH / ratio
const newFov = THREE.Math.radToDeg(Math.atan(newH)) * 2
camera.fov = newFov
} else {
camera.fov = myFov
}
camera.updateProjectionMatrix()
//https://github.com/mrdoob/three.js/issues/15968
const hFOV = 50; // desired horizontal fov, in degrees
camera.fov = Math.atan( Math.tan( hFOV * Math.PI / 360 ) / camera.aspect ) * 360 / Math.PI; // degrees
camera.updateProjectionMatrix();
* Handle cloud like soft transparent texture geo properly
const tmp_map = child.material.map
const tmp_mat = new THREE.MeshBasicMaterial({
map: child.material.map,
side: THREE.DoubleSide,
transparent: true
})
child.material = tmp_mat
* Handle leaf like transparent texture geo properly:
* ref: https://github.com/KhronosGroup/glTF-Blender-IO/issues/1264
* code
// reuse use the png texture, create basic material replace with blend mode to alpha clip and clip at 0.5
const tmp_map = child.material.map
const tmp_mat = new THREE.MeshBasicMaterial({
map: child.material.map,
side: THREE.DoubleSide,
alphaMode:"MASK",
alphaTest:0.5
})
child.material = tmp_mat
// or set properly in blender before bring into threejs
// ref: https://www.youtube.com/watch?v=03isI0_FGLU&t=61s
inside blender,
transparent node->1-> mix shader
use map-c> diffuse node ->2-> mix shader
use map-a> mix shader.fac
mix shader -> out.surface
then side panel:
make material option :
* blend mode: alpha clip
* and set roughness to 1
* set texture node filter and optionally disable mip map use
child.material.map.inFilter = THREE.NearestFilter
child.material.map.generateMipmaps = false
* make object ignore camera frustum culling, so that when camera move closer, object won't disappear
if(child.name.startsWith("sch_word_")){
child.frustumCulled = false
}
* video as texture
// ref: https://stemkoski.github.io/Three.js/
// https://threejs.org/docs/#api/en/textures/VideoTexture
// 1. define global variable for controls
let video = undefined
let videoImage = undefined
let videoImageContext = undefined
let videoTexture = undefined
let mov_mat = undefined
// 2. (after all your model loaded event) create video material
video = document.createElement( 'video' );
video.src = "video/my_video.mp4";
video.load();
// video.play(); // (don't play on start)
videoImage = document.createElement( 'canvas' );
videoImage.width = 640;
videoImage.height = 480;
videoImageContext = videoImage.getContext( '2d' );
// background color if no video present
videoImageContext.fillStyle = '#000000';
videoImageContext.fillRect( 0, 0, videoImage.width, videoImage.height );
videoTexture = new THREE.Texture( videoImage );
videoTexture.minFilter = THREE.LinearFilter;
videoTexture.magFilter = THREE.LinearFilter;
mov_mat = new THREE.MeshBasicMaterial( { map: videoTexture, side:THREE.DoubleSide } ); // overdraw: true,
// 3. assign to geo
my_video_plane_geo.material = mov_mat;
// 4. call video_update() during render each frame
function video_update(){
if ( video.readyState === video.HAVE_ENOUGH_DATA )
{
videoImageContext.drawImage( video, 0, 0 );
if ( videoTexture )
videoTexture.needsUpdate = true;
}
}
// 5. support functions for call from other event
function video_start(){
//bgm_sound.pause(); // option to mute bgm sound if you have any
video.currentTime = 0;
video.play();
}
function video_stop(){
//bgm_sound.play(); // option to on bgm sound if you have any
video.pause();
video.currentTime = 0;
}
* language based texture switch
// method 1: (works in firefox, not in chrome)
if(child.name == "lang_texture_geo") ){
lang_texture_geo = child
if(main_lang=="cn"){
lang_texture_geo.material.map.image.src = "./img/cn_texture.jpg";
}
}
// method 2: (works in both firefox and chrome)
const main_texLoader = new THREE.TextureLoader(load_manager)
const cn_text_ctex = main_texLoader.load("./img/cn_texture.jpg");
cn_text_ctex.flipY = false;
if(child.name == "lang_texture_geo") ){
lang_texture_geo = child
if(main_lang == "cn"){
child.material.map = cn_text_ctex;
}
}
====== Template ======
Good Animation
Good One
====== template 2 ======
Three
1
Some Description Here
//import './style.css'
//import * as THREE from 'three'
//import { OrbitControls } from './script/OrbitControls.js'
console.log('Hello Three.js')
//console.log(THREE)
const scene = new THREE.Scene()
//-- loading manager
const load_ui = document.querySelector('.loadbar')
const load_manager = new THREE.LoadingManager(
// loaded
() => {
gsap.delayedCall(0.5, () =>{
console.log('loaded')
gsap.to(cover_mesh.material.uniforms.uAlpha, {duration: 3, value: 0 })
load_ui.classList.add('ended')
load_ui.style.transform = ''
})
},
// progress
(itemUrl, itemLoaded, itemTotal) => {
console.log('loading')
const progress_ratio = itemLoaded / itemTotal
load_ui.style.transform = `scaleX(${progress_ratio})`
}
)
// -- cover-object
// original 3d space =
// gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0)
const cover_mesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(2,2,1,1),
new THREE.ShaderMaterial({
// wireframe: true,
transparent: true,
uniforms:{
uAlpha:{value:1}
},
vertexShader:`
void main(){
gl_Position = vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float uAlpha;
void main(){
gl_FragColor = vec4(0,0,0, uAlpha);
}
`
})
)
scene.add(cover_mesh)
//--points
const points = [
{
position: new THREE.Vector3(1.55, 0.3, -0.6),
element: document.querySelector('.point-0')
}
]
// Mesh = Geo + Shader
const box_geo = new THREE.BoxGeometry(1,2,1)
// -- mat (0xff0000, 'red', '#ff0000', 'rgb(255,0,0)', 'rgb(100%,0%,0%)',)
// -- 'hsl(0,100%,50%)', 1,0,0
const default_mat = new THREE.MeshBasicMaterial( {color: '#ff0000'} )
// -- mesh
const box_mesh = new THREE.Mesh(box_geo, default_mat)
box_mesh.position.x=2
box_mesh.scale.set(2,1,1)
const blue_box_mesh = new THREE.Mesh(new THREE.SphereBufferGeometry(.5,16,16), new THREE.MeshBasicMaterial( {color: '#0000ff'}) )
scene.add(blue_box_mesh)
const green_box_mesh = new THREE.Mesh(new THREE.BoxBufferGeometry(1,1,1,2,2,2), new THREE.MeshBasicMaterial( {color: '#00ff00', wireframe: true}) )
scene.add(green_box_mesh)
green_box_mesh.position.x = 1
green_box_mesh.position.z = 1
green_box_mesh.rotation.reorder('YXZ')
// -- custom geo
const pos_array = new Float32Array([
0,0,0,
0,4,0,
4,0,0
])
const pos_attr = new THREE.BufferAttribute(pos_array, 3)
const pos_geo = new THREE.BufferGeometry()
pos_geo.setAttribute('position', pos_attr)
const cust_mesh = new THREE.Mesh(pos_geo, new THREE.MeshBasicMaterial( {color: 0x00ff00, wireframe: true}) )
scene.add(cust_mesh)
//box_mesh.rotation.reorder('YXZ') // set order first if needed
box_mesh.rotation.y=Math.PI*0.5 // pi value radient value
//scene.add(box_mesh)
// -- texture
// ----- step method
/*
const door_img_file = new Image()
const door_img_tex = new THREE.Texture(door_img_file)
door_img_file.onload = () => {
door_img_tex.needsUpdate = true
}
door_img_file.src = './img/door/color.jpg'
default_mat.map = door_img_tex
default_mat.color = undefined
*/
// ----- simple method
//const loading_manager = new THREE.LoadingManager()
//loading_manager.onStart = ()=>{}
const door_texLoader = new THREE.TextureLoader(load_manager) // optional parent loading_manager
const door_ctex = door_texLoader.load('./img/door/color.jpg')
const door_atex = door_texLoader.load('./img/door/alpha.jpg')
const door_htex = door_texLoader.load('./img/door/height.jpg')
const door_ntex = door_texLoader.load('./img/door/normal.jpg')
const door_aotex = door_texLoader.load('./img/door/ambientOcclusion.jpg')
const door_mtex = door_texLoader.load('./img/door/metalness.jpg')
const door_rtex = door_texLoader.load('./img/door/roughness.jpg')
const particle_ctex = door_texLoader.load('./img/sprite/2.png')
// const brown_mctex = door_texLoader.load('./img/matcap/7B5254_E9DCC7_B19986_C8AC91.jpg')
const brown_mctex = door_texLoader.load('./img/matcap/746761_291C19_AB9385_3C2B27.jpg')
/*
door_ctex.repeat.x = 1.5
door_ctex.repeat.y = 1.5
door_ctex.wrapS = THREE.MirroredRepeatWrapping
door_ctex.wrapT = THREE.MirroredRepeatWrapping
door_ctex.offset.x = .5
door_ctex.offset.y = .5
door_ctex.center.x = 0.5 // for rotateCenter
door_ctex.center.y = 0.5
door_ctex.rotation = Math.PI*.25
*/
//door_ctex.generateMipmaps = false // for those blocky style texture
//door_ctex.minFilter = THREE.NearestFilter // linearFilter: blur
//door_ctex.magFilter = THREE.NearestFilter // NearestFilter: sharp blocky pixel
default_mat.map = door_ctex // after intialized
default_mat.color = undefined
// default_mat.color = new THREE.Color('pink')
default_mat.transparent = true
// default_mat.opacity = 0.5
door_atex.minFilter = THREE.NearestFilter // fix distance tex blur issue
default_mat.alphaMap = door_atex
default_mat.side = THREE.DoubleSide
//blue_box_mesh.material.flatShading = true
// - change to lambert
// blue_box_mesh.material = new THREE.MeshLambertMaterial({color:'blue'}) // need light
// blue_box_mesh.material = new THREE.MeshPhongMaterial({color:'blue'}) // need light
// blue_box_mesh.material = new THREE.MeshToonMaterial({color:'blue'}) // need light, can take gradientMap
blue_box_mesh.material = new THREE.MeshStandardMaterial({color:'grey'}) // need light, more PBR map options
// -- light
const main_light = new THREE.DirectionalLight(0xffffff, 1)
main_light.position.set(1,1,1)
scene.add(main_light)
// ----- environment method
const env_cubeTexLoader = new THREE.CubeTextureLoader(load_manager)
const env_tex = env_cubeTexLoader.load([
'./img/env/4r/px.png',
'./img/env/4r/nx.png',
'./img/env/4r/py.png',
'./img/env/4r/ny.png',
'./img/env/4r/pz.png',
'./img/env/4r/nz.png'
])
scene.background = env_tex
scene.environment = env_tex // global mesh envMap setup
blue_box_mesh.material.envMap = env_tex
blue_box_mesh.material.metalness= .8
blue_box_mesh.material.roughness= .1
const white_mat = new THREE.MeshStandardMaterial({
color:'#eee',
roughness: 0.3,
metalness: 0.2,
side: THREE.DoubleSide
})
const base_floor_mesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(5,5,2,2), white_mat)
base_floor_mesh.rotation.x = Math.PI*0.5
base_floor_mesh.position.y = -2
scene.add(base_floor_mesh)
base_floor_mesh.receiveShadow = true
const base_box_mesh = new THREE.Mesh(new THREE.BoxBufferGeometry(0.8,0.8,2,2), white_mat)
base_box_mesh.position.y = -1.5
scene.add(base_box_mesh)
base_box_mesh.castShadow = true
const base_areaLight = new THREE.RectAreaLight(0x4e00ff,5, 1, 1)
base_areaLight.position.set(1.5,-1,1.5)
base_areaLight.lookAt(base_box_mesh.position)
scene.add(base_areaLight)
const base_spotLight = new THREE.SpotLight(0x78fff00,0.5, 4, Math.PI*0.25, .25, 1) // 2nd last, softness
base_spotLight.position.set(-1.5,-1,1.5)
scene.add(base_spotLight)
scene.add(base_spotLight.target)
base_spotLight.target.position.set(0,-1.5,0)
base_spotLight.castShadow = true
base_spotLight.shadow.mapSize.width = 1024
base_spotLight.shadow.mapSize.height = 1024
base_spotLight.shadow.radius = 10 // softness
// for smooth object, to avoid self shadow casting, you need to tweak per light shadow map
//base_spotLight.shadow.normalBias = 0.05 // so push shadow calcultion into geo to avoid geo self shadow
// for flat object, try shadow.bias
// shadow also can be limited but clipping plane from lgiht
const base_spotLight_helper = new THREE.SpotLightHelper(base_spotLight,0xffffff)
scene.add(base_spotLight_helper)
// -- mesh instance
// group
const main_grp = new THREE.Group()
scene.add(main_grp)
main_grp.add(box_mesh)
main_grp.position.x=1
//-- font
const text_mat = new THREE.MeshMatcapMaterial({matcap:brown_mctex})
text_mat.wireframe = false
// text_mat.flatShading = true
const main_fontLoader = new THREE.FontLoader(load_manager)
main_fontLoader.load(
'./font/helvetiker_regular.typeface.json',
(result_font)=>{
console.log('font loaded')
const bevel_a = 0.02
const bevel_b = 0.03
const text_geo = new THREE.TextBufferGeometry(
'Shining & Lucy',{
font: result_font,
size: 0.5,
height: .05,
curveSegments: 3,
bevelEnabled: true,
bevelThickness: bevel_b,
bevelSize: bevel_a,
bevelOffset: 0,
bevelSegments: 2
}
)
text_geo.computeBoundingBox() // max3, min3
text_geo.center()
const text_mesh = new THREE.Mesh(text_geo, text_mat)
// text_mesh.position.y = -2
scene.add(text_mesh)
}
)
// -- random geo
console.time('randomGeo')
for (let i=0; i<100;i++){
const tmp_geo = new THREE.TorusBufferGeometry(0.3,0.2, 20,45)
const tmp_mesh = new THREE.Mesh(tmp_geo, text_mat)
tmp_mesh.position.x = (Math.random()-0.5)*10
tmp_mesh.position.y = (Math.random()-0.5)*10
tmp_mesh.position.z = (Math.random()-0.5)*10
tmp_mesh.rotation.x = (Math.random()-0.5)*Math.PI
tmp_mesh.rotation.y = (Math.random()-0.5)*Math.PI
tmp_s = Math.random()
tmp_mesh.scale.set(tmp_s, tmp_s, tmp_s)
scene.add(tmp_mesh)
}
console.timeEnd('randomGeo')
// camera (x,Y,z) , same coordinate system as Maya,
// (x for right, z for close, Y for up)
/*
const output_size = {
width: 800,
height: 400
}
*/
// -- particle
const particle_geo = new THREE.SphereBufferGeometry(1,32,32)
const particle_mat = new THREE.PointsMaterial({
size: 0.02,
sizeAttenuation: true
})
const particle_sys = new THREE.Points(particle_geo, particle_mat)
scene.add(particle_sys)
// - color
const color_up = new THREE.Color(0xff0000)
const color_dn = new THREE.Color(0x0000ff)
console.log(particle_geo.attributes.position.array[1])
const ramp_color_list = new Float32Array(particle_geo.attributes.position.count*3)
for (let i = 0; i{
console.log(the_gltf)
const boat_obj = the_gltf.scene.children[2]
boat_obj.scale.set(0.5,0.5,0.5)
boat_obj.position.y= 2
boat_obj.material = new THREE.MeshStandardMaterial({color:'white'})
// this is for per mat env setup, use global scene env instead
// boat_obj.material.envMap = env_tex
scene.add(boat_obj) // it add to our scene and remove from gltf scene
console.log(boat_obj)
/*
// loop method
while(the_gltf.scene.children.length){
scene.add(the_gltf.scene.children[0])
}
// copy method
const child_list = [...the_gltf.scene.children]
for (const child of child_list){
scene.add(child)
}
// animation, animation is under each node
mixer = new THREE.AnimationMixer(the_gltf.scene)
const action = mixer.ClipAction(the_gltf.animation[0])
action.play()
*/
//updateAllMaterial()
})
// update all mat
const updateAllMaterial = ()=>{
scene.traverse((child)=>{
if (child instanceof THREE.Mesh && child.material instanceof THREE.MeshStandardMaterial){
child.castShadow = true
child.receiveShadow = true
// child.needsUpdate = true
}
})
}
// full screen
const output_size = {
width: window.innerWidth,
height: window.innerHeight
}
let ratio = output_size.width / output_size.height
const main_cam = new THREE.PerspectiveCamera(75, ratio )
// main_cam.position.z = 5
main_cam.position.set(0,0,5)
scene.add(main_cam)
// aim Action, like Maya aimConstraint and delete
// new THREE.Vector3(0,0,0)
main_cam.lookAt(box_mesh.position)
// axis
const main_axis = new THREE.AxesHelper()
scene.add(main_axis)
// main_render
const output = document.querySelector('canvas.view')
const main_render = new THREE.WebGLRenderer(
{
canvas: output,
antialias: true, // need to be setup initially
}
)
main_render.setSize(output_size.width, output_size.height)
main_render.setPixelRatio(Math.min(window.devicePixelRatio, 2) ) // limit to 2x render
// physical light style
main_render.physicallyCorrectLights = true // abit dimmer
// render encoding, default linear
main_render.outputEncoding = THREE.sRGBEncoding
// tone map (NoToneMapping, LinearToneMapping, ReinhardToneMapping, CineonToneMapping, ACESFilmicToneMapping)
// main_render.toneMapping = THREE.ACESFilmicToneMapping
// main_render.toneMappingExposure = 3
// - enable shadow globally
// -- mesh,light who make shadow needs Enabled (point, direct, spot)
// -- mesh receive shadow need enabled
// main_render.shadowMap.enabled = true
// shadow draw method
//(PCFShadowMap, fast:BasicShadowMap, better:PCFSoftShadowMap)
// main_render.shadowMap.type = THREE.PCFSoftShadowMap // no shadow blur option
// event
let mouse = {
x: 0,
y: 0
}
window.addEventListener('mousemove', (event)=>{
//console.log(event.clientX) // from top left corner
// top/left to center coordinate
mouse.x = (event.clientX / output_size.width)*2-1
mouse.y = -(event.clientY / output_size.height)*2+1
})
window.addEventListener('resize', ()=>{
//console.log("new width:"+window.innerWidth)
output_size.width = window.innerWidth
output_size.height = window.innerHeight
// update cam
ratio = output_size.width / output_size.height
main_cam.aspect = ratio
main_cam.updateProjectionMatrix()
// update render
main_render.setSize(output_size.width, output_size.height)
})
window.addEventListener('dblclick', ()=>{
console.log()
const full_screen_element = document.fullscreenElement || document.webkitFullscreenElement
//output.requestFullScreen()
if (!full_screen_element){
if (output.requestFullscreen){
// normal browse support
output.requestFullscreen()
} else if (output.webkitRequestFullscreen){
// safari
output.webkitRequestFullscreen()
}
}else{
if (document.exitFullscreen){
// normal browse support
document.exitFullscreen()
} else if (document.webkitExitFullscreen){
// safari
document.webkitExitFullscreen()
}
}
})
// -- gui
const gui = new dat.GUI({ closed:true, width: 300})
const global_ctrl = {
green_box: {
y: 0,
color:0x00ff00,
slide: ()=>{
let tl = gsap.timeline();
const cur_x = green_box_mesh.position.x
tl.to(green_box_mesh.position, {duration: 2, x: cur_x+2})
.to(green_box_mesh.position, {duration: 1, x: cur_x})
},
deleteParticle: ()=>{
particle_random_sys.geometry.dispose()
scene.remove(particle_random_sys)
}
}
}
gui.add(global_ctrl.green_box, 'y', 0, 5, 0.1).name('posY').onChange( ()=> {
green_box_mesh.position.y = global_ctrl.green_box.y
} )
gui.addColor(global_ctrl.green_box, 'color').onChange(
()=>{
green_box_mesh.material.color.set(global_ctrl.green_box.color)
}
)
gui.add(global_ctrl.green_box, 'slide')
gui.add(global_ctrl.green_box, 'deleteParticle')
gui.add(green_box_mesh, 'visible')
gui.add(green_box_mesh.material, 'wireframe')
// -- cam ctrl
const orbit_ctrl = new THREE.OrbitControls( main_cam, main_render.domElement );
orbit_ctrl.enableDamping = true // enable ease after mouse movement
// -- animation gsap
gsap.to(blue_box_mesh.position, {duration: 2, delay: 2, x:-4})
gsap.to(blue_box_mesh.position, {duration: 2, delay: 1, x:2})
// -- animation
var frame = 1
let start_time = Date.now() // js clock
const main_clock = new THREE.Clock() // three clock
let pre_time_3 = 0
const tick = () => {
if (frame%60 == 0){
//console.log(frame)
const cur_time = Date.now()
const pass_time = cur_time - start_time
//console.log(pass_time/1000)
const pass_time_3 = main_clock.getElapsedTime()
const delta_time_3 = pass_time_3 - pre_time_3
pre_time_3 = pass_time_3
//console.log('clock:'+pass_time_3)
//console.log(mouse)
//console.log('pixel: '+window.devicePixelRatio)
}
frame = frame + 1
// update
if (box_mesh.position.x<5){
box_mesh.position.x+=0.01
}
box_mesh.rotation.y+=0.01
// update green by mouse
green_box_mesh.rotation.y = Math.PI*mouse.x*0.5
green_box_mesh.rotation.x = Math.PI*mouse.y*-0.25
// time
orbit_ctrl.update()
// light helper update
base_spotLight_helper.update()
// areaLightHelper more
/*
base_areaLight_helper.position.copy(base_areaLight.position)
base_areaLight_helper.quaternion.copy(base_areaLight.quaternion)
base_areaLight_helper.update()
*/
/*
// animation
if (mixer!== null){
mixer.update(delta_time_3)
}
*/
// update points
for(const point of points){
const screen_pos = point.position.clone()
screen_pos.project(main_cam)
const tx = screen_pos.x * output_size.width * 0.5
const ty = screen_pos.y * output_size.height * 0.5 * -1
point.element.style.transform = `translate(${tx}px, ${ty}px)`
}
// re-render
main_render.render(scene, main_cam)
window.requestAnimationFrame(tick) // loop for next frame
}
tick()
// distanceTo built-in
console.log("ObjToCam: "+box_mesh.position.distanceTo(main_cam.position))