Commit 6b88f019 by Иван Кубота

initial commit

parents
# 8th Wall Web Examples - three.js - Tap to place
This interactive example allows the user to grow trees on the ground by tapping. This showcases raycasting, instantiating objects, importing 3D models, and animation.
![tapplace-threejs-screenshot](../../../images/screenshot-tap.jpg)
[Try the live demo here](https://templates.8thwall.app/placeground-threejs)
## Overview
On 'touchstart' (when the user taps the screen), a THREE.Raycaster() is used to determine where the intersection with the ground (a transparent THREE.PlaneGeometry residing at a height of Y=0) occurs. THREE.GLTFLoader() is then used to load a .glb file and place it at the tap location on the ground. The model is instantiated with a random Y-rotation and the initial scale is set to a very small value. tween.js is then used to apply a scale-up animation to the model.
;(function() {
'use strict'
var root = this
var has_require = typeof require !== 'undefined'
var THREE = root.THREE || (has_require && require('three'))
if (!THREE) throw new Error('MeshLine requires three.js')
function MeshLine() {
Object.assign(this, new THREE.BufferGeometry())
this.type = 'MeshLine'
this.positions = []
this.firstTime = true;
this.previous = []
this.next = []
this.side = []
this.width = []
this.indices_array = []
this.uvs = []
this.counters = []
this._points = []
this._geom = null
this.widthCallback = null
// Used to raycast
this.matrixWorld = new THREE.Matrix4()
Object.defineProperties(this, {
// this is now a bufferGeometry
// add getter to support previous api
geometry: {
enumerable: true,
get: function() {
return this
},
},
geom: {
enumerable: true,
get: function() {
return this._geom
},
set: function(value) {
this.setGeometry(value, this.widthCallback)
},
},
// for declaritive architectures
// to return the same value that sets the points
// eg. this.points = points
// console.log(this.points) -> points
points: {
enumerable: true,
get: function() {
return this._points
},
set: function(value) {
this.setPoints(value, this.widthCallback)
},
},
})
}
MeshLine.prototype = Object.create(THREE.BufferGeometry.prototype)
MeshLine.prototype.constructor = MeshLine
MeshLine.prototype.isMeshLine = true
MeshLine.prototype.setMatrixWorld = function(matrixWorld) {
this.matrixWorld = matrixWorld
}
// setting via a geometry is rather superfluous
// as you're creating a unecessary geometry just to throw away
// but exists to support previous api
MeshLine.prototype.setGeometry = function(g, c) {
// as the input geometry are mutated we store them
// for later retreival when necessary (declaritive architectures)
this._geometry = g;
if (g instanceof THREE.Geometry) {
this.setPoints(g.vertices, c);
} else if (g instanceof THREE.BufferGeometry) {
this.setPoints(g.getAttribute("position").array, c);
} else {
this.setPoints(g, c);
}
}
MeshLine.prototype.setPoints = function(points, wcb) {
if (!(points instanceof Float32Array) && !(points instanceof Array)) {
console.error(
"ERROR: The BufferArray of points is not instancied correctly."
);
return;
}
// as the points are mutated we store them
// for later retreival when necessary (declaritive architectures)
this._points = points;
this.widthCallback = wcb;
//this.positions.length = 0;// = [];
//this.counters.length = 0;// = [];
var _p = 0, _c = 0;
if (points.length && points[0] instanceof THREE.Vector3) {
// could transform Vector3 array into the array used below
// but this approach will only loop through the array once
// and is more performant
for (var j = 0; j < points.length; j++) {
var p = points[j];
var c = j / points.length;
this.positions[_p++] = p.x;
this.positions[_p++] = p.y;
this.positions[_p++] = p.z;
this.positions[_p++] = p.x;
this.positions[_p++] = p.y;
this.positions[_p++] = p.z;
this.counters[_c++] = c;
this.counters[_c++] = c;
}
} else {
for (var j = 0; j < points.length; j += 3) {
var c = j / points.length;
this.positions[_p++] = points[j];
this.positions[_p++] = points[j+1];
this.positions[_p++] = points[j+2];
this.positions[_p++] = points[j];
this.positions[_p++] = points[j+1];
this.positions[_p++] = points[j+2];
this.counters[_c++] = c;
this.counters[_c++] = c;
}
}
this.process();
}
MeshLine.prototype.setPoints = function(points, wcb) {
if (!(points instanceof Float32Array) && !(points instanceof Array)) {
console.error(
"ERROR: The BufferArray of points is not instancied correctly."
);
return;
}
// as the points are mutated we store them
// for later retreival when necessary (declaritive architectures)
this._points = points;
this.widthCallback = wcb;
if (points.length && points[0] instanceof THREE.Vector3) {
this.positions.length = 0;// = [];
this.counters.length = 0;// = [];
// could transform Vector3 array into the array used below
// but this approach will only loop through the array once
// and is more performant
for (var j = 0; j < points.length; j++) {
var p = points[j];
var c = j / points.length;
this.positions.push(p.x, p.y, p.z);
this.positions.push(p.x, p.y, p.z);
this.counters.push(c);
this.counters.push(c);
}
} else {
var _p = 0, _c = 0, positions = this.positions, counters = this.counters;
for (var j = 0; j < points.length; j += 3) {
var c = j / points.length;
positions[_p++] = points[j];
positions[_p++] = points[j+1];
positions[_p++] = points[j+2];
positions[_p++] = points[j];
positions[_p++] = points[j+1];
positions[_p++] = points[j+2];
counters[_c++] = c;
counters[_c++] = c;
}
}
this.process();
}
function MeshLineRaycast(raycaster, intersects) {
var inverseMatrix = new THREE.Matrix4()
var ray = new THREE.Ray()
var sphere = new THREE.Sphere()
var interRay = new THREE.Vector3()
var geometry = this.geometry
// Checking boundingSphere distance to ray
sphere.copy(geometry.boundingSphere)
sphere.applyMatrix4(this.matrixWorld)
if (raycaster.ray.intersectSphere(sphere, interRay) === false) {
return
}
inverseMatrix.getInverse(this.matrixWorld)
ray.copy(raycaster.ray).applyMatrix4(inverseMatrix)
var vStart = new THREE.Vector3()
var vEnd = new THREE.Vector3()
var interSegment = new THREE.Vector3()
var step = this instanceof THREE.LineSegments ? 2 : 1
var index = geometry.index
var attributes = geometry.attributes
if (index !== null) {
var indices = index.array
var positions = attributes.position.array
var widths = attributes.width.array
for (var i = 0, l = indices.length - 1; i < l; i += step) {
var a = indices[i]
var b = indices[i + 1]
vStart.fromArray(positions, a * 3)
vEnd.fromArray(positions, b * 3)
var width = widths[Math.floor(i / 3)] != undefined ? widths[Math.floor(i / 3)] : 1
var precision = raycaster.params.Line.threshold + (this.material.lineWidth * width) / 2
var precisionSq = precision * precision
var distSq = ray.distanceSqToSegment(vStart, vEnd, interRay, interSegment)
if (distSq > precisionSq) continue
interRay.applyMatrix4(this.matrixWorld) //Move back to world space for distance calculation
var distance = raycaster.ray.origin.distanceTo(interRay)
if (distance < raycaster.near || distance > raycaster.far) continue
intersects.push({
distance: distance,
// What do we want? intersection point on the ray or on the segment??
// point: raycaster.ray.at( distance ),
point: interSegment.clone().applyMatrix4(this.matrixWorld),
index: i,
face: null,
faceIndex: null,
object: this,
})
// make event only fire once
i = l
}
}
}
MeshLine.prototype.raycast = MeshLineRaycast
MeshLine.prototype.compareV3 = function(a, b) {
var aa = a * 6
var ab = b * 6
return (
this.positions[aa] === this.positions[ab] &&
this.positions[aa + 1] === this.positions[ab + 1] &&
this.positions[aa + 2] === this.positions[ab + 2]
)
}
var compareV3P = function(a, b, p) {
var aa = a * 6
var ab = b * 6
return (
p[aa] === p[ab] &&
p[aa + 1] === p[ab + 1] &&
p[aa + 2] === p[ab + 2]
)
}
var copyArr = [0,0,0];
MeshLine.prototype.copyV3 = function(a) {
var aa = a * 6, positions = this.positions;
copyArr[0] = positions[aa];
copyArr[1] = positions[aa+1];
copyArr[2] = positions[aa+2];
return copyArr;//[this.positions[aa], this.positions[aa + 1], this.positions[aa + 2]]
}
var copyV3P = function(a, p) {
var aa = a * 6;
copyArr[0] = p[aa];
copyArr[1] = p[aa+1];
copyArr[2] = p[aa+2];
return copyArr;//[this.positions[aa], this.positions[aa + 1], this.positions[aa + 2]]
}
MeshLine.prototype.process = (function(){
return function(){
var _p = 0, _n = 0, _w = 0, _s = 0, _u = 0, _i = 0
var positions = this.positions, previous = this.previous;
var l = this.positions.length / 6
//this.previous.length = 0;//
//this.next.length = 0;// = []
//this.side.length = 0;// = []
//this.width.length = 0;// = []
// this.indices_array.length = 0;// = []
//this.uvs.length = 0;// = []
var w
var v
// initial previous points
if( compareV3P( 0, l - 1, positions ) ){
v = copyV3P( l - 2, positions )
}else{
v = copyV3P( 0, positions )
}
previous[ _p++ ] = v[ 0 ];
previous[ _p++ ] = v[ 1 ];
previous[ _p++ ] = v[ 2 ];
previous[ _p++ ] = v[ 0 ];
previous[ _p++ ] = v[ 1 ];
previous[ _p++ ] = v[ 2 ];
var side = this.side,
width = this.width,
uvs = this.uvs,
previous = this.previous,
next = this.next,
indices_array = this.indices_array;
for( var j = 0; j < l; j++ ){
// sides
side[ _s++ ] = 1;
side[ _s++ ] = -1;
// widths
w = 1
width[ _w++ ] = w;
width[ _w++ ] = w;
// uvs
uvs[ _u++ ] = j / ( l - 1 );
uvs[ _u++ ] = 0;
uvs[ _u++ ] = j / ( l - 1 );
uvs[ _u++ ] = 1;
if( j < l - 1 ){
// points previous to poisitions
v = copyV3P( j, positions )
previous[ _p++ ] = v[ 0 ];
previous[ _p++ ] = v[ 1 ];
previous[ _p++ ] = v[ 2 ];
previous[ _p++ ] = v[ 0 ];
previous[ _p++ ] = v[ 1 ];
previous[ _p++ ] = v[ 2 ];
// indices
var n = j * 2
indices_array[ _i++ ] = n;
indices_array[ _i++ ] = n + 1;
indices_array[ _i++ ] = n + 2;
indices_array[ _i++ ] = n + 2;
indices_array[ _i++ ] = n + 1;
indices_array[ _i++ ] = n + 3;
}
if( j > 0 ){
// points after poisitions
v = copyV3P( j, positions )
next[ _n++ ] = v[ 0 ];
next[ _n++ ] = v[ 1 ];
next[ _n++ ] = v[ 2 ];
next[ _n++ ] = v[ 0 ];
next[ _n++ ] = v[ 1 ];
next[ _n++ ] = v[ 2 ];
}
}
// last next point
if( compareV3P( l - 1, 0, positions ) ){
v = copyV3P( 1, positions )
}else{
v = copyV3P( l - 1, positions )
}
next[ _n++ ] = v[ 0 ];
next[ _n++ ] = v[ 1 ];
next[ _n++ ] = v[ 2 ];
next[ _n++ ] = v[ 0 ];
next[ _n++ ] = v[ 1 ];
next[ _n++ ] = v[ 2 ];
// redefining the attribute seems to prevent range errors
// if the user sets a differing number of vertices
if( this.firstTime ){
this.firstTime = false;
this._attributes = {
position: new THREE.BufferAttribute( new Float32Array( this.positions ), 3 ),
previous: new THREE.BufferAttribute( new Float32Array( this.previous ), 3 ),
next: new THREE.BufferAttribute( new Float32Array( this.next ), 3 ),
side: new THREE.BufferAttribute( new Float32Array( this.side ), 1 ),
width: new THREE.BufferAttribute( new Float32Array( this.width ), 1 ),
uv: new THREE.BufferAttribute( new Float32Array( this.uvs ), 2 ),
index: new THREE.BufferAttribute( new Uint16Array( this.indices_array ), 1 ),
counters: new THREE.BufferAttribute( new Float32Array( this.counters ), 1 ),
}
}else{
this._attributes.position.copyArray( this.positions )
this._attributes.position.needsUpdate = true
this._attributes.previous.copyArray( this.previous )
this._attributes.previous.needsUpdate = true
this._attributes.next.copyArray( this.next )
this._attributes.next.needsUpdate = true
/*this._attributes.side.copyArray(this.side)
this._attributes.side.needsUpdate = true
this._attributes.width.copyArray(this.width)
this._attributes.width.needsUpdate = true
this._attributes.uv.copyArray(this.uvs)
this._attributes.uv.needsUpdate = true
this._attributes.index.copyArray(this.indices_array)
this._attributes.index.needsUpdate = true*/
}
this.setAttribute( 'position', this._attributes.position )
this.setAttribute( 'previous', this._attributes.previous )
this.setAttribute( 'next', this._attributes.next )
this.setAttribute( 'side', this._attributes.side )
this.setAttribute( 'width', this._attributes.width )
this.setAttribute( 'uv', this._attributes.uv )
this.setAttribute( 'counters', this._attributes.counters )
this.setIndex( this._attributes.index )
this.computeBoundingSphere()
this.computeBoundingBox()
}
})();
function memcpy(src, srcOffset, dst, dstOffset, length) {
var i
src = src.subarray || src.slice ? src : src.buffer
dst = dst.subarray || dst.slice ? dst : dst.buffer
src = srcOffset
? src.subarray
? src.subarray(srcOffset, length && srcOffset + length)
: src.slice(srcOffset, length && srcOffset + length)
: src
if (dst.set) {
dst.set(src, dstOffset)
} else {
for (i = 0; i < src.length; i++) {
dst[i + dstOffset] = src[i]
}
}
return dst
}
/**
* Fast method to advance the line by one position. The oldest position is removed.
* @param position
*/
MeshLine.prototype.advance = function(position) {
var positions = this._attributes.position.array
var previous = this._attributes.previous.array
var next = this._attributes.next.array
var l = positions.length
// PREVIOUS
memcpy(positions, 0, previous, 0, l)
// POSITIONS
memcpy(positions, 6, positions, 0, l - 6)
positions[l - 6] = position.x
positions[l - 5] = position.y
positions[l - 4] = position.z
positions[l - 3] = position.x
positions[l - 2] = position.y
positions[l - 1] = position.z
// NEXT
memcpy(positions, 6, next, 0, l - 6)
next[l - 6] = position.x
next[l - 5] = position.y
next[l - 4] = position.z
next[l - 3] = position.x
next[l - 2] = position.y
next[l - 1] = position.z
this._attributes.position.needsUpdate = true
this._attributes.previous.needsUpdate = true
this._attributes.next.needsUpdate = true
}
THREE.ShaderChunk['meshline_vert'] = [
'',
THREE.ShaderChunk.logdepthbuf_pars_vertex,
THREE.ShaderChunk.fog_pars_vertex,
'',
'attribute vec3 previous;',
'attribute vec3 next;',
'attribute float side;',
'attribute float width;',
'attribute float counters;',
'',
'uniform vec2 resolution;',
'uniform float lineWidth;',
'uniform vec3 color;',
'uniform float opacity;',
'uniform float sizeAttenuation;',
'',
'varying vec2 vUV;',
'varying vec4 vColor;',
'varying float vCounters;',
'',
'vec2 fix( vec4 i, float aspect ) {',
'',
' vec2 res = i.xy / i.w;',
' res.x *= aspect;',
' vCounters = counters;',
' return res;',
'',
'}',
'',
'void main() {',
'',
' float aspect = resolution.x / resolution.y;',
'',
' vColor = vec4( color, opacity );',
' vUV = uv;',
'',
' mat4 m = projectionMatrix * modelViewMatrix;',
' vec4 finalPosition = m * vec4( position, 1.0 );',
' vec4 prevPos = m * vec4( previous, 1.0 );',
' vec4 nextPos = m * vec4( next, 1.0 );',
'',
' vec2 currentP = fix( finalPosition, aspect );',
' vec2 prevP = fix( prevPos, aspect );',
' vec2 nextP = fix( nextPos, aspect );',
'',
' float w = lineWidth * width;',
'',
' vec2 dir;',
' if( nextP == currentP ) dir = normalize( currentP - prevP );',
' else if( prevP == currentP ) dir = normalize( nextP - currentP );',
' else {',
' vec2 dir1 = normalize( currentP - prevP );',
' vec2 dir2 = normalize( nextP - currentP );',
' dir = normalize( dir1 + dir2 );',
'',
' vec2 perp = vec2( -dir1.y, dir1.x );',
' vec2 miter = vec2( -dir.y, dir.x );',
' //w = clamp( w / dot( miter, perp ), 0., 4. * lineWidth * width );',
'',
' }',
'',
' //vec2 normal = ( cross( vec3( dir, 0. ), vec3( 0., 0., 1. ) ) ).xy;',
' vec4 normal = vec4( -dir.y, dir.x, 0., 1. );',
' normal.xy *= .5 * w;',
' normal *= projectionMatrix;',
' if( sizeAttenuation == 0. ) {',
' normal.xy *= finalPosition.w;',
' normal.xy /= ( vec4( resolution, 0., 1. ) * projectionMatrix ).xy;',
' }',
'',
' finalPosition.xy += normal.xy * side;',
'',
' gl_Position = finalPosition;',
'',
THREE.ShaderChunk.logdepthbuf_vertex,
THREE.ShaderChunk.fog_vertex && ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',
THREE.ShaderChunk.fog_vertex,
'}',
].join('\n')
THREE.ShaderChunk['meshline_frag'] = [
'',
THREE.ShaderChunk.fog_pars_fragment,
THREE.ShaderChunk.logdepthbuf_pars_fragment,
'',
'uniform sampler2D map;',
'uniform sampler2D alphaMap;',
'uniform float useMap;',
'uniform float useAlphaMap;',
'uniform float useDash;',
'uniform float dashArray;',
'uniform float dashOffset;',
'uniform float dashRatio;',
'uniform float visibility;',
'uniform float alphaTest;',
'uniform vec2 repeat;',
'',
'varying vec2 vUV;',
'varying vec4 vColor;',
'varying float vCounters;',
'',
'void main() {',
'',
THREE.ShaderChunk.logdepthbuf_fragment,
'',
' vec4 c = vColor;',
' if( useMap == 1. ) c *= texture2D( map, vUV * repeat );',
' if( useAlphaMap == 1. ) c.a *= texture2D( alphaMap, vUV * repeat ).a;',
' if( c.a < alphaTest ) discard;',
' if( useDash == 1. ){',
' c.a *= ceil(mod(vCounters + dashOffset, dashArray) - (dashArray * dashRatio));',
' }',
' c.a = 1.0 - abs(1.0-vUV.y*2.0);',
' gl_FragColor = c;',
' gl_FragColor.a *= step(vCounters, visibility);',
'',
THREE.ShaderChunk.fog_fragment,
'}',
].join('\n')
function MeshLineMaterial(parameters) {
this.isShadowMaterial = false;
this.isMeshStandardMaterial = false;
this.isMeshLambertMaterial = false;
this.isMeshToonMaterial = false;
this.isMeshPhongMaterial = false;
this.needsLights = false;
Object.assign(this, this.shm = new THREE.ShaderMaterial({
uniforms: Object.assign({}, THREE.UniformsLib.fog, {
lineWidth: { value: 1 },
map: { value: null },
useMap: { value: 0 },
alphaMap: { value: null },
useAlphaMap: { value: 0 },
color: { value: new THREE.Color(0xffffff) },
opacity: { value: 1 },
resolution: { value: new THREE.Vector2(1, 1) },
sizeAttenuation: { value: 1 },
dashArray: { value: 0 },
dashOffset: { value: 0 },
dashRatio: { value: 0.5 },
useDash: { value: 0 },
visibility: { value: 1 },
alphaTest: { value: 0 },
repeat: { value: new THREE.Vector2(1, 1) },
}),
vertexShader: THREE.ShaderChunk.meshline_vert,
fragmentShader: THREE.ShaderChunk.meshline_frag,
}));
this._opacity = this.uniforms.opacity
this.type = 'MeshLineMaterial'
this.map = null;
this.alphaMap = null;
this.sizeAttenuation = false;
Object.defineProperties(this, {
lineWidth: {
enumerable: true,
get: function() {
return this.uniforms.lineWidth.value
},
set: function(value) {
this.uniforms.lineWidth.value = value
},
},
useMap: {
enumerable: true,
get: function() {
return this.uniforms.useMap.value
},
set: function(value) {
this.uniforms.useMap.value = value
},
},
useAlphaMap: {
enumerable: true,
get: function() {
return this.uniforms.useAlphaMap.value
},
set: function(value) {
this.uniforms.useAlphaMap.value = value
},
},
color: {
enumerable: true,
get: function() {
return this.uniforms.color.value
},
set: function(value) {
this.uniforms.color.value = value
},
},
/* opacity: {
enumerable: true,
get: function() {
return this.uniforms.opacity.value
},
set: function(value) {
this.uniforms.opacity.value = value
},
},*/
resolution: {
enumerable: true,
get: function() {
return this.uniforms.resolution.value
},
set: function(value) {
this.uniforms.resolution.value.copy(value)
},
},
sizeAttenuation: {
enumerable: true,
get: function() {
return this.uniforms.sizeAttenuation.value
},
set: function(value) {
this.uniforms.sizeAttenuation.value = value
},
},
dashArray: {
enumerable: true,
get: function() {
return this.uniforms.dashArray.value
},
set: function(value) {
this.uniforms.dashArray.value = value
this.useDash = value !== 0 ? 1 : 0
},
},
dashOffset: {
enumerable: true,
get: function() {
return this.uniforms.dashOffset.value
},
set: function(value) {
this.uniforms.dashOffset.value = value
},
},
dashRatio: {
enumerable: true,
get: function() {
return this.uniforms.dashRatio.value
},
set: function(value) {
this.uniforms.dashRatio.value = value
},
},
useDash: {
enumerable: true,
get: function() {
return this.uniforms.useDash.value
},
set: function(value) {
this.uniforms.useDash.value = value
},
},
visibility: {
enumerable: true,
get: function() {
return this.uniforms.visibility.value
},
set: function(value) {
this.uniforms.visibility.value = value
},
},
alphaTest: {
enumerable: true,
get: function() {
return this.uniforms.alphaTest.value
},
set: function(value) {
this.uniforms.alphaTest.value = value
},
},
repeat: {
enumerable: true,
get: function() {
return this.uniforms.repeat.value
},
set: function(value) {
this.uniforms.repeat.value.copy(value)
},
},
})
this.setValues(parameters)
}
MeshLineMaterial.prototype = Object.create(THREE.ShaderMaterial.prototype)
MeshLineMaterial.prototype.constructor = MeshLineMaterial
MeshLineMaterial.prototype.isMeshLineMaterial = true
MeshLineMaterial.prototype.copy = function(source) {
THREE.ShaderMaterial.prototype.copy.call(this, source)
this.lineWidth = source.lineWidth
this.map = source.map
this.useMap = source.useMap
this.alphaMap = source.alphaMap
this.useAlphaMap = source.useAlphaMap
this.color.copy(source.color)
this.opacity = source.opacity
this.resolution.copy(source.resolution)
this.sizeAttenuation = source.sizeAttenuation
this.dashArray.copy(source.dashArray)
this.dashOffset.copy(source.dashOffset)
this.dashRatio.copy(source.dashRatio)
this.useDash = source.useDash
this.visibility = source.visibility
this.alphaTest = source.alphaTest
this.repeat.copy(source.repeat)
return this
}
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = {
MeshLine: MeshLine,
MeshLineMaterial: MeshLineMaterial,
MeshLineRaycast: MeshLineRaycast,
}
}
exports.MeshLine = MeshLine
exports.MeshLineMaterial = MeshLineMaterial
exports.MeshLineRaycast = MeshLineRaycast
} else {
root.MeshLine = MeshLine
root.MeshLineMaterial = MeshLineMaterial
root.MeshLineRaycast = MeshLineRaycast
}
}.call(this))
\ No newline at end of file
window.apearingChild = function(from, to) {
return function( child ){
var newMaterial = child.material.clone();
newMaterial.transparent = true;
newMaterial.onBeforeCompile = function( shader ){
shader.transparent = true;
shader.vertexShader = shader.vertexShader
.replace( `void main() {`, `varying vec4 vPosition;
void main() {
vPosition = modelMatrix * vec4(position, 1.0);` )
;
var Ffrom = from.toFixed(1),
Fto = to.toFixed(1),
Fdiff = (to-from).toFixed(1);
shader.fragmentShader = shader.fragmentShader.replace(
`uniform float`,
`
varying vec4 vPosition;
varying float noise;
uniform float`
).replace(
'#include <dithering_fragment>',
`#include <dithering_fragment>
float time = opacity;
vec4 pos = vPosition;
if(time<${Ffrom}){
gl_FragColor.a = 0.0;// *= sin(vUv.y*10.0+uTime)>0.5?1.0:0.0;
}else if(time >${Fto}){
gl_FragColor.a = 1.0;
}else{
vec3 color = mix(vec3(gl_FragColor), vec3(1.0,1.0,1.0), (${Fto}-time)/${Fdiff});
gl_FragColor = vec4( color.rgb, -pos.y/(time-372.0)*10.0 );
}
` );
}
child.material = newMaterial;
/*child.children[0].material = newMaterial*/
this.newMaterial = newMaterial;
}
}
window.initScene = function({scene, camera, renderer}){
const GLTFLoader = THREE.GLTFLoader;
const loader = new GLTFLoader();
Object.assign( camera.position, window.cameraPosition || {
x: -1.7743804661688403,
y: 1.1573565480428425,
z: -5.243816954496617
} );
Object.assign( camera.rotation, { x: -3.10768077311436387, y: -0.49165510718706528, z: -0.009893627612469875 } );
window.camera = camera
var mixers = [];
var clock;
var lines = [];
var mat1, sha1;
var cachedMaterials = {};
var version = false;
var GetMaterial = function( i ){
var lm = new MeshLineMaterial( {
"useMap": false,
"color": new THREE.Color( 0xffffee ),
"opacity": 0,
"resolution": {
"x": 1920,
"y": 1080
},
"sizeAttenuation": false,
"lineWidth": 15,
"depthWrite": false,
"depthTest": true,
"alphaTest": 0,
"transparent": true,
"side": THREE.DoubleSide
} );
if( version !== false )
lm.version = version;
version = lm.version;
return lm;
}
var LineFromVtx = function( vtx ){
var lineObj = {
lines: [],
points: [ [ 0, 0, 0 ], [ 1, 1, 1 ] ].flat(),
vtx: vtx
};
var i = 0.52;
var line = new MeshLine();
//line.setPoints( lineObj.points );
lineObj.lines.push( line )
var material = GetMaterial( i );
line.mesh = new THREE.Mesh( line, material )
line.mesh.line = line
line.opacity = 1 - i * 1.4;
line._opacity = line.mesh.material._opacity;
return lineObj;
}
var subScenes = [];
var inFps = 30, outFps = 30;
var keyMaterial;
var setLoopTime = function( time ){
time *= inFps;
var cfg = this.loopTimeCfg;
if( !cfg )
return this.setTime( time * outFps );
if( time < cfg.start ){
return this.setTime( 0 );
}else if( cfg.loop === false ){
time = Math.min( time, cfg.to - 0.001 );
}
time = ( ( ( time - cfg.start ) % ( cfg.to - cfg.from ) ) + cfg.from ) / outFps;
if( cfg.fn )
cfg.fn( time * outFps, this );
return this.setTime( time );
};
var sceneLines = [];
var loops = window.modelCfg;
loader.load(
window.modelFileName,
function( gltf ){
var subScene = gltf.scene.clone();
var group = new THREE.Group();
outer.group = group;
var notUsedAnimations = gltf.animations[ 0 ].clone(),
notUsedAnimationsHash = {};
notUsedAnimations.tracks.forEach( clip => notUsedAnimationsHash[ clip.name ] = clip );
if( window.childExtractor ){
subScene.children = window.childExtractor( subScene.children );
}
subScene.children = subScene.children.filter( ( child, n ) => {
var wLines = window.lines;
for( var key in wLines ){
if( child.name.indexOf( key ) === 0 ){
var lineScene = sceneLines[ key ];
if( !lineScene ){
sceneLines[ key ] = {
scene: gltf.scene.clone(),
lines: []
};
lineScene = sceneLines[ key ];
lineScene.scene.children.length = 0;
}
var lineObj = {
vtx: child,
line: LineFromVtx( child ),
points: []
};
lineObj.mixer = new THREE.AnimationMixer( lineScene.scene );
lineObj.mixer.setLoopTime = setLoopTime;
lineObj.mixer.loopTimeCfg = wLines[ key ];
lineScene.lines.push( lineObj );
return false;
}
}
if( child.name in window.objects ){
window.objects[ child.name ].name = child.name;
window.objects[ child.name ].obj = child;
if( !child ){
debugger
}
window.objects[ child.name ].fn( child, subScene );
}
return true;
} );
for( var key in sceneLines ){
var lineScene = sceneLines[ key ];
lineScene.lines.forEach( line => {
var child = line.vtx, lineObj = line;
lineScene.scene.add( line.line.vtx );
var trackStart = child.name + '.'
child.animations = [ gltf.animations[ 0 ].clone() ].map( a => {
a.tracks = a.tracks.filter( t => {
if( t.name.indexOf( trackStart ) === 0 ){
delete notUsedAnimationsHash[ t.name ]
return true;
}
return false;
} );
lineObj.mixer.clipAction( a ).play();
return a;
} )
line.line.lines.forEach( line => {
group.add( line.mesh );
} );
} );
}
subScene.mixer = new THREE.AnimationMixer( subScene );
notUsedAnimations.tracks = Object.values( notUsedAnimationsHash );
subScene.mixer.clipAction( notUsedAnimations ).play();
group.add( subScene );
scene.add(group);
scene.mixer = subScene.mixer;
clock = new THREE.Clock()
animate();
window.lateCB && window.lateCB();
} );
window.addEventListener( 'resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
render();
}, false );
const stats = Stats();
document.body.appendChild( stats.dom );
var TIME = 0;
var animateScene = false;
var animate = function(){
var delta = clock.getDelta();
if(animateScene){
TIME += delta;
for( var key in window.lines ){
var lineScene = sceneLines[ key ];
for( var j = 0, _j = lineScene.lines.length; j < _j; j++ ){
var line = lineScene.lines[ j ];
var timeLength = line.vtx.scale.x * 1.5, interpolations = 40;
var _p = 0;
var linePoints = line.points,
lineVtxPosition = line.vtx.position;
for( var i = 0; i < interpolations; i++ ){
line.mixer.setLoopTime( TIME + timeLength / interpolations * i );
linePoints[ _p++ ] = lineVtxPosition.x;
linePoints[ _p++ ] = lineVtxPosition.y;
linePoints[ _p++ ] = lineVtxPosition.z;
}
var cfg = line.mixer.loopTimeCfg, time = TIME * inFps - cfg.start;
if( cfg.opacity ){
var children = line.line.lines,
opacity = time > cfg.opacity + cfg.start ? 1 : ( time - cfg.start < cfg.opacity - 90 ? 0 : 1 + ( time - cfg.opacity - cfg.start ) / 90 );
for( var i = 0, _i = children.length; i < _i; i++ ){
var child = children[ i ];
child._opacity.value = opacity * child.opacity;
}
}
for( var k = 0, _k = line.line.lines.length; k < _k; k++ ){
line.line.lines[ k ].setPoints( line.points );
}
line.mixer.setLoopTime( TIME );
}
}
scene.mixer.setTime( Math.min( TIME, window.sceneAnimationLength / inFps ) );
for( var i in window.objects ){
if( window.objects[ i ].step ){
window.objects[ i ].step( TIME * inFps )
}
}
}
requestAnimationFrame( animate );
/*controls.update();*/
//render();
stats.update();
};
function render(){
renderer.render( scene, camera );
}
var outer = {
show: function() {
animateScene = true;
},
setTime: function(time) {
TIME = time;
},
getTime: function() {
return TIME;
},
scene,
camera
};
return outer;
}
\ No newline at end of file
body {
overflow: hidden;
width: 100%;
height: 100%;
margin: -1px 0px 0px 0px !important;
padding: 0;
}
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>8th Wall Web: three.js</title>
<link rel="stylesheet" type="text/css" href="index.css">
<script src="./build/three.js"></script>
<script src="./js/controls/OrbitControls.js"></script>
<script src="./js/loaders/GLTFLoader.js"></script>
<script src="./js/libs/stats.min.js"></script>
<script src="./THREE.MeshLine.js"></script>
<script type="importmap">
{
"imports": {
"three": "./build/three.module.js",
"three/examples/jsm/controls/OrbitControls":"./jsm/controls/OrbitControls.js",
"three/examples/jsm/loaders/GLTFLoader":"./jsm/loaders/GLTFLoader.js",
"three/examples/jsm/libs/stats.module":"./jsm/libs/stats.module.js"
}
}
</script>
<!-- Javascript tweening engine -->
<script src="//cdnjs.cloudflare.com/ajax/libs/tween.js/16.3.5/Tween.min.js"></script>
<!-- XR Extras - provides utilities like load screen, almost there, and error handling.
See github.com/8thwall/web/tree/master/xrextras -->
<script src="//cdn.8thwall.com/web/xrextras/xrextras.js"></script>
<!-- 8thWall Web - Replace the app key here with your own app key -->
<script async src="//apps.8thwall.com/xrweb?appKey=klnDucOXOK7ZT5PAQR6JjXlAhABM7iBIvCwYLwPr66HcYAmklUYgMZush6qvISq89ixmlt"></script>
<!-- client code -->
<script>
var modelFileName = 'MainSceneKey.glb',
lines = {
/* 'Key': { from: 0, to: 500, start: 0, loop: false, fn: function(time, smth) {
/!*keyMaterial && (keyMaterial.opacity = time);*!/
/!*if(time<350){
coolMaterial.uniforms[ 'time' ].value = time;
smth._root.children[0].material = coolMaterial;
}else{
smth._root.children[0].material = keyMaterial;
}*!/
} },*/
'Point': { from: 0, to: 370, start: 0, loop: false, opacity: 30},
'Birdoak10':{ from: 0, to: 300, start: 0, opacity: 320 },
'Birdoak1_': { from: 0, to: 300, start: 100, opacity: 50000 },
'Birdoak2': { from: 0, to: 330, start: 0, opacity: 100 },
'Birdoak3': { from: 0, to: 300, start: 92220, opacity: 50 },
'Birdoak4': { from: 0, to: 300, start: 22260, opacity: 30 },
'Birdoak5': { from: 0, to: 300, start: 0, opacity: 460 },
'Birdoak6': { from: 0, to: 300, start: 10440, opacity: 5000 },
'Birdoak7': { from: 0, to: 300, start: 83330, opacity: 18000 },
'Birdloak8': { from: 0, to: 330, start: 51110, opacity: 133310 },
'Birdoak9': { from: 0, to: 300, start: 0, opacity: 65000 },
},
objects = {
'KeyLP001': {
step: function(time) {
if(!this.newMaterial)
return;
this.newMaterial.opacity = time;
this.obj.material.opacity = time;
//this.obj.children[0].material.opacity = time;
},
fn: function(child) {
this.fn = apearingChild(272,370);
this.fn(child)
}
}
},
sceneAnimationLength = 480;
var sceneItems = ['Key','Point','Birdoak1','Birdoak2','Birdoak3','Birdoak4','Birdoak5','Birdoak6','Birdoak7','Birdoak8','Birdoak9','Birdoak10'];
</script>
<script type="module" src="client5.js"></script>
<script src="index.js"></script>
<style>
#distance {
text-align: right;
position: absolute;
right: 0;
background: rgba(0,0,0,0.5);
color: #fff;
padding: 6px 8px;
font-family: monospace;
}
</style>
</head>
<body>
<canvas id="camerafeed"></canvas>
<div id="distance"></div>
</body>
</html>
// Copyright (c) 2021 8th Wall, Inc.
// Returns a pipeline module that initializes the threejs scene when the camera feed starts, and
// handles subsequent spawning of a glb model whenever the scene is tapped.
/* globals XR8 XRExtras THREE TWEEN */
var lastTarget;
var sceneIsPlaying = false;
var nextPosition,
currentPosition;
var updateNextPosition = function(detail) {
nextPosition = {
position: detail.position,
rotation: (new THREE.Quaternion()).copy(detail.rotation),
scale: detail.scale,
scaledWidth: detail.scaledWidth,
scaledHeight: detail.scaledHeight
}
if(!currentPosition) {
currentPosition = nextPosition;
}
};
var Lerp = function(a, b, t){
return a + (b - a) * t
};
var tmpVector = new THREE.Vector3(0,0,0);
var updateWorld = function(dt) {
var t = Math.min(dt/300,0.5);
currentPosition.position = (new THREE.Vector3()).lerpVectors(currentPosition.position, nextPosition.position, t);
currentPosition.rotation = currentPosition.rotation.slerp(nextPosition.rotation, t);
currentPosition.scale = Lerp(currentPosition.scale, nextPosition.scale, t);
currentPosition.scaledWidth = Lerp(currentPosition.scaledWidth, nextPosition.scaledWidth, t);
currentPosition.scaledHeight = Lerp(currentPosition.scaledHeight, nextPosition.scaledHeight, t);
plane1.position.copy(currentPosition.position);
plane1.quaternion.copy(currentPosition.rotation);
plane1.scale.x=currentPosition.scale*currentPosition.scaledWidth;
plane1.scale.y=currentPosition.scale*currentPosition.scaledHeight;
var point = plane1.position.clone();
var rotation = plane1.rotation.clone();
rotation.x+=Math.PI/2;
point.add((new THREE.Vector3(0,0,0)).applyEuler(rotation))
plane2.position.copy(point);
plane2.rotation.copy(plane1.rotation)
plane2.rotateX(-Math.PI/180*85)
//plane2.position.add(((new THREE.Vector3(0,lastTarget.scale*0.57,-lastTarget.scale*0.33)).applyEuler(plane1.rotation)))
plane2.scale.y = plane2.scale.x = plane1.scale.x;
plane2.position.add(((new THREE.Vector3(0,plane1.scale.y/2,0)).applyEuler(plane1.rotation)))
plane2.position.add(((new THREE.Vector3(0,plane2.scale.x/2,0)).applyEuler(plane2.rotation)))
var o = camera;
if(!sceneControl || !sceneControl.group)
return;
if(!sceneIsPlaying) {
sceneControl.show()
sceneControl.setTime( wrapz.time() )
sceneIsPlaying = true;
}
Object.assign(sceneControl.group.position, plane2.position);//wrapz.position(o.position, o, lastTarget))
//Object.assign(sceneControl.group.rotation, wrapz.rotation(plane2.rotation))
Object.assign(sceneControl.group.quaternion, wrapz.q(plane2.quaternion))
sceneControl.group.rotateX(Math.PI/2)
sceneControl.group.scale.x = sceneControl.group.scale.y = sceneControl.group.scale.z = plane2.scale.x;
}
var showTarget = ({detail}) => {
console.log('show', detail.name);
window.lastTarget = detail;
updateNextPosition(detail)
//Object.assign(sceneControl.group.quaternion, wrapz.q(o.quaternion))
//Object.assign(sceneControl.group.position, wrapz.position(o.position, o, lastTarget))
if (detail.name === 'model-target1') {
model.position.copy(detail.position)
model.quaternion.copy(detail.rotation)
model.scale.set(detail.scale, detail.scale, detail.scale)
model.visible = true
}
}
var dst = document.querySelector('#distance');
var updateTarget = ({detail}) => {
updateNextPosition(detail)
console.log('update', detail.name);
/*plane1.position.copy(currentPosition.position);
plane1.quaternion.copy(currentPosition.rotation);
plane1.scale.x=currentPosition.scale*currentPosition.scaledWidth;
plane1.scale.y=currentPosition.scale*currentPosition.scaledHeight;
var point = plane1.position.clone();
var rotation = plane1.rotation.clone();
rotation.x+=Math.PI/2;
point.add((new THREE.Vector3(0,0,0)).applyEuler(rotation))
plane2.position.copy(point);
plane2.rotation.copy(plane1.rotation)
plane2.rotateX(-Math.PI/180*85)
//plane2.position.add(((new THREE.Vector3(0,lastTarget.scale*0.57,-lastTarget.scale*0.33)).applyEuler(plane1.rotation)))
plane2.scale.y = plane2.scale.x = plane1.scale.x;
plane2.position.add(((new THREE.Vector3(0,plane1.scale.y/2,0)).applyEuler(plane1.rotation)))
plane2.position.add(((new THREE.Vector3(0,plane2.scale.x/2,0)).applyEuler(plane2.rotation)))
if(!sceneControl || !sceneControl.group)
return;
Object.assign(sceneControl.group.position, plane2.position);*/
}
var hideTarget = ({detail}) => {
console.log('hide', detail.name)
if (detail.name === 'model-target1') {
model.position.copy(detail.position)
model.quaternion.copy(detail.rotation)
model.scale.set(detail.scale, detail.scale, detail.scale)
model.visible = true
}
}
var sceneControl;
var o = window.o = new THREE.Object3D();
var plane1, plane2;
const placegroundScenePipelineModule = () => {
const modelFile = 'tree.glb' // 3D model to spawn at tap
const startScale = new THREE.Vector3(0.01, 0.01, 0.01) // Initial scale value for our model
const endScale = new THREE.Vector3(2, 2, 2) // Ending scale value for our model
const animationMillis = 750 // Animate over 0.75 seconds
const raycaster = new THREE.Raycaster()
const tapPosition = new THREE.Vector2()
const loader = new THREE.GLTFLoader() // This comes from GLTFLoader.js.
let surface // Transparent surface for raycasting for object placement.
// Populates some object into an XR scene and sets the initial camera position. The scene and
// camera come from xr3js, and are only available in the camera loop lifecycle onStart() or later.
const initXrScene = ({scene, camera, renderer}) => {
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
sceneControl = initScene({scene, camera, renderer})
/*const light = new THREE.DirectionalLight(0xffffff, 1, 100)
light.position.set(1, 4.3, 2.5) // default
scene.add(light) // Add soft white light to the scene.*/
scene.add(new THREE.AmbientLight(0x404040, 5)) // Add soft white light to the scene.
/*light.shadow.mapSize.width = 1024 // default
light.shadow.mapSize.height = 1024 // default
light.shadow.camera.near = 0.5 // default
light.shadow.camera.far = 500 // default
light.castShadow = true*/
surface = new THREE.Mesh(
new THREE.PlaneGeometry(100, 100, 1, 1),
new THREE.ShadowMaterial({
opacity: 0.5,
})
)
plane1 = new THREE.Mesh(
new THREE.PlaneGeometry(1,1,1,1),
new THREE.MeshStandardMaterial({color: 0xff0000, wireframe: true})
);
scene.add(plane1);
plane2 = new THREE.Mesh(
new THREE.PlaneGeometry(1,1,1,1),
new THREE.MeshStandardMaterial({color: 0x00cc77, wireframe: true})
);
scene.add(plane2);
surface.rotateX(-Math.PI / 2)
surface.position.set(0, 0, 0)
surface.receiveShadow = true
scene.add(surface)
// Set the initial camera position relative to the scene we just laid out. This must be at a
// height greater than y=0.
camera.position.set(0, 3, 0)
}
const animateIn = (model, pointX, pointZ, yDegrees) => {
const scale = {...startScale}
model.scene.rotation.set(0.0, yDegrees, 0.0)
model.scene.position.set(pointX, 0.0, pointZ)
model.scene.scale.set(scale.x, scale.y, scale.z)
model.scene.children[0].children[0].children[0].castShadow = true
XR8.Threejs.xrScene().scene.add(model.scene)
new TWEEN.Tween(scale)
.to(endScale, animationMillis)
.easing(TWEEN.Easing.Elastic.Out) // Use an easing function to make the animation smooth.
.onUpdate(() => {
model.scene.scale.set(scale.x, scale.y, scale.z)
})
.start() // Start the tween immediately.
}
// Load the glb model at the requested point on the surface.
const placeObject = (pointX, pointZ) => {
loader.load(
modelFile, // resource URL.
(gltf) => {
animateIn(gltf, pointX, pointZ, Math.random() * 360)
}
)
}
const placeObjectTouchHandler = (e) => {
// Call XrController.recenter() when the canvas is tapped with two fingers. This resets the
// AR camera to the position specified by XrController.updateCameraProjectionMatrix() above.
if (e.touches.length === 2) {
XR8.XrController.recenter()
}
if (e.touches.length > 2) {
return
}
// If the canvas is tapped with one finger and hits the "surface", spawn an object.
const {camera} = XR8.Threejs.xrScene()
// calculate tap position in normalized device coordinates (-1 to +1) for both components.
tapPosition.x = (e.touches[0].clientX / window.innerWidth) * 2 - 1
tapPosition.y = -(e.touches[0].clientY / window.innerHeight) * 2 + 1
// Update the picking ray with the camera and tap position.
raycaster.setFromCamera(tapPosition, camera)
// Raycast against the "surface" object.
const intersects = raycaster.intersectObject(surface)
if (intersects.length === 1 && intersects[0].object === surface) {
placeObject(intersects[0].point.x, intersects[0].point.z)
}
}
return {
// Pipeline modules need a name. It can be whatever you want but must be unique within your app.
name: 'placeground',
listeners: [
{event: 'reality.imagefound', process: showTarget},
{event: 'reality.imageupdated', process: updateTarget},
{event: 'reality.imagelost', process: hideTarget},
],
// onStart is called once when the camera feed begins. In this case, we need to wait for the
// XR8.Threejs scene to be ready before we can access it to add content. It was created in
// XR8.Threejs.pipelineModule()'s onStart method.
onStart: ({canvas}) => {
const {scene, camera, renderer} = window.xrScene = XR8.Threejs.xrScene() // Get the 3js sceen from xr3js.
// Add objects to the scene and set starting camera position.
initXrScene({scene, camera, renderer})
//canvas.addEventListener('touchstart', placeObjectTouchHandler, true) // Add touch listener.
// prevent scroll/pinch gestures on canvas
/*canvas.addEventListener('touchmove', (event) => {
event.preventDefault()
})*/
// Enable TWEEN animations.
var t = +new Date();
const animate = (time) => {
requestAnimationFrame(animate)
if(!window.sceneControl || !sceneControl.group)
return;
if(currentPosition)
updateWorld(-(t - (t = +new Date())));
!dst && (dst = document.querySelector('#distance'));
dst.innerText = 'Distance: '+(camera.position.distanceTo(plane1.position)/plane1.scale.x).toFixed(1)+'\n';
//o.rotation.y+=0.1*sceneControl.getTime();
//TWEEN.update(time)
}
animate()
// Sync the xr controller's 6DoF position and camera paremeters with our scene.
XR8.XrController.updateCameraProjectionMatrix({
origin: camera.position,
facing: camera.quaternion,
})
},
}
}
window.wrapz = {
position: function(p) {
return p;
},
rotation: function(p) {
return p;
},
q: function(p) {
return p;
},
time: function(t) {
return 0;
}
};
wrapz.position = (a,o, d)=>{
var point = o.position.clone();
var v = new THREE.Vector3(0,-1,-4)
v.applyEuler(o.rotation);
point.add(v)
return point;
}
const onxrloaded = () => {
XR8.addCameraPipelineModules([ // Add camera pipeline modules.
// Existing pipeline modules.
XR8.GlTextureRenderer.pipelineModule(), // Draws the camera feed.
XR8.Threejs.pipelineModule(), // Creates a ThreeJS AR Scene.
XR8.XrController.pipelineModule(), // Enables SLAM tracking.
XRExtras.AlmostThere.pipelineModule(), // Detects unsupported browsers and gives hints.
XRExtras.FullWindowCanvas.pipelineModule(), // Modifies the canvas to fill the window.
XRExtras.Loading.pipelineModule(), // Manages the loading screen on startup.
XRExtras.RuntimeError.pipelineModule(), // Shows an error image on runtime error.
// Custom pipeline modules.
placegroundScenePipelineModule(),
])
// Open the camera and start running the camera run loop.
XR8.run({canvas: document.getElementById('camerafeed')})
}
// Show loading screen before the full XR library has been loaded.
const load = () => { XRExtras.Loading.showLoading({onxrloaded}) }
window.onload = () => { window.XRExtras ? load() : window.addEventListener('xrextrasloaded', load) }
File added
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment