littlejsengine 1.20.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/littlejs.d.ts +408 -26
- package/dist/littlejs.esm.js +3360 -161
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +3344 -161
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +1448 -141
- package/package.json +1 -1
- package/plugins/gltf.js +54 -8
- package/plugins/math3d.js +90 -10
- package/plugins/pluginExport.js +10 -0
- package/plugins/postProcess.js +41 -12
- package/plugins/render3d.js +424 -55
- package/plugins/render3dDebug.js +2 -1
- package/plugins/render3dEditor.js +1812 -0
- package/plugins/render3dExtras.js +199 -5
- package/plugins/render3dLevel.js +189 -0
- package/plugins/render3dVoxels.js +12 -0
- package/plugins/sceneSystem.js +58 -0
- package/plugins/tweenSystem.js +4 -2
- package/src/engine.js +199 -34
- package/src/engineAudio.js +2 -2
- package/src/engineBuild.mjs +3 -0
- package/src/engineDebug.js +55 -1
- package/src/engineDraw.js +97 -6
- package/src/engineEditor.js +41 -20
- package/src/engineExport.js +6 -0
- package/src/engineInput.js +8 -4
- package/src/engineLogo.js +19 -0
- package/src/engineObject.js +3 -2
- package/src/engineRelease.js +13 -1
- package/src/engineSettings.js +38 -0
package/package.json
CHANGED
package/plugins/gltf.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* GLTFObject that createObject makes; a skinned character's walk is not read
|
|
9
9
|
* - Materials give a base color and texture and whether they blend; glass made with KHR_materials_transmission blends too,
|
|
10
10
|
* and a KHR_materials_unlit material comes in emissive, its own color with no shading
|
|
11
|
+
* - A material's normal map and emissive map load too, with its normal scale and emissive factor, read at the base
|
|
12
|
+
* color texture's uvs; roughness, metalness and occlusion maps are not loaded
|
|
11
13
|
* - The base color texture reads the uv set its texCoord names, moved by KHR_texture_transform as gltfpack and
|
|
12
14
|
* Blender write it
|
|
13
15
|
* - An OPAQUE material, the default, ignores its texture's alpha as the format says: a texture only such materials
|
|
@@ -56,6 +58,17 @@ class GLTFPart
|
|
|
56
58
|
/** @property {boolean} - The material is unlit (KHR_materials_unlit), its own color with no shading; the object
|
|
57
59
|
* createObject makes draws it with emissive 1 */
|
|
58
60
|
this.unlit = false;
|
|
61
|
+
/** @property {TextureInfo|undefined} - The material's normal map, drawn with the base color texture's uvs
|
|
62
|
+
* @type {TextureInfo|undefined} */
|
|
63
|
+
this.normalMap = undefined;
|
|
64
|
+
/** @property {number} - The normal map's strength, its scale in the file */
|
|
65
|
+
this.normalScale = 1;
|
|
66
|
+
/** @property {TextureInfo|undefined} - The material's emissive map, or a white texture when it has an
|
|
67
|
+
* emissiveFactor and no texture, so it glows all over
|
|
68
|
+
* @type {TextureInfo|undefined} */
|
|
69
|
+
this.emissiveMap = undefined;
|
|
70
|
+
/** @property {Color} - The emissiveFactor, which multiplies the emissive map */
|
|
71
|
+
this.emissiveMapColor = WHITE;
|
|
59
72
|
}
|
|
60
73
|
}
|
|
61
74
|
|
|
@@ -201,8 +214,8 @@ class GLTFModel
|
|
|
201
214
|
for (const part of this.parts)
|
|
202
215
|
part.mesh.dispose();
|
|
203
216
|
this.mesh.dispose();
|
|
204
|
-
for (const textureInfo of new Set(this.parts.
|
|
205
|
-
textureInfo?.destroyWebGLTexture();
|
|
217
|
+
for (const textureInfo of new Set(this.parts.flatMap(p=> [p.textureInfo, p.normalMap, p.emissiveMap])))
|
|
218
|
+
textureInfo !== gltfWhiteTextureInfo && textureInfo?.destroyWebGLTexture(); // the white one is shared
|
|
206
219
|
}
|
|
207
220
|
|
|
208
221
|
/** Make an object at a position with a child per part, so each keeps its own texture, color and blending, and
|
|
@@ -260,6 +273,8 @@ class GLTFObject extends EngineObject3D
|
|
|
260
273
|
o.transparent = part.transparent;
|
|
261
274
|
o.pixelated = part.pixelated;
|
|
262
275
|
o.emissive = part.unlit ? 1 : 0;
|
|
276
|
+
o.normalMap = part.normalMap, o.normalScale = part.normalScale;
|
|
277
|
+
o.emissiveMap = part.emissiveMap, o.emissiveMapColor = part.emissiveMapColor.copy(); // its own, as color is
|
|
263
278
|
const rest = model.nodeTree?.restPose?.[part.node];
|
|
264
279
|
if (rest)
|
|
265
280
|
{
|
|
@@ -396,13 +411,15 @@ async function parseGLTF(data, baseUrl='')
|
|
|
396
411
|
return gltfFetch(buffer.uri, baseUrl).then(r=> r.arrayBuffer());
|
|
397
412
|
}));
|
|
398
413
|
|
|
399
|
-
// the textures, decoded together first; none without WebGL, and a failed image only logs;
|
|
400
|
-
// textures are drawn with,
|
|
401
|
-
const
|
|
414
|
+
// the textures, decoded together first; none without WebGL, and a failed image only logs; the base color, normal
|
|
415
|
+
// and emissive textures are drawn with, the roughness and occlusion maps are not loaded at all
|
|
416
|
+
const usedTextures = new Set((json.materials || []).flatMap(m=> [m.pbrMetallicRoughness?.baseColorTexture?.index,
|
|
417
|
+
m.normalTexture?.index, m.emissiveTexture?.index]));
|
|
418
|
+
const normalTextures = new Set((json.materials || []).map(m=> m.normalTexture?.index));
|
|
402
419
|
const opaqueTextures = gltfOpaqueTextures(json);
|
|
403
420
|
const textures = await Promise.all((json.textures || []).map(async (texture, index)=>
|
|
404
421
|
{
|
|
405
|
-
if (!glContext || typeof createImageBitmap == 'undefined' || !
|
|
422
|
+
if (!glContext || typeof createImageBitmap == 'undefined' || !usedTextures.has(index)) return;
|
|
406
423
|
try
|
|
407
424
|
{
|
|
408
425
|
// a WebP or AVIF image is named by its extension, the browser decodes those like any other
|
|
@@ -419,7 +436,11 @@ async function parseGLTF(data, baseUrl='')
|
|
|
419
436
|
// an OPAQUE material ignores its texture's alpha, which the 3D pass would cut holes by, so a texture only
|
|
420
437
|
// those use has it set to 1; decoded as stored for that, and a jpeg has no alpha to set
|
|
421
438
|
const opaque = opaqueTextures.has(index) && blob.type !== 'image/jpeg';
|
|
422
|
-
|
|
439
|
+
// a normal map is directions, not colors, so it is decoded as stored; one a material also uses as a
|
|
440
|
+
// color, an odd file, is decoded that way too
|
|
441
|
+
const bitmap = normalTextures.has(index) ?
|
|
442
|
+
await createImageBitmap(blob, {colorSpaceConversion: 'none', premultiplyAlpha: 'none'}) :
|
|
443
|
+
opaque ? await createImageBitmap(blob, {premultiplyAlpha: 'none'}).then(gltfOpaqueImage) : await createImageBitmap(blob);
|
|
423
444
|
return new TextureInfo(bitmap, true, [sampler.wrapS ?? 10497, sampler.wrapT ?? 10497]); // REPEAT by default
|
|
424
445
|
}
|
|
425
446
|
catch (e) { LOG('glTF image not loaded', e); }
|
|
@@ -495,7 +516,7 @@ async function parseGLTF(data, baseUrl='')
|
|
|
495
516
|
}
|
|
496
517
|
|
|
497
518
|
// a texture none of the parts draws with, another scene's, goes now too, since dispose finds them by the parts
|
|
498
|
-
const used = new Set(parts.
|
|
519
|
+
const used = new Set(parts.flatMap(p=> [p.textureInfo, p.normalMap, p.emissiveMap]));
|
|
499
520
|
for (const texture of textures)
|
|
500
521
|
texture && !used.has(texture) && texture.destroyWebGLTexture();
|
|
501
522
|
return new GLTFModel(parts, animations, {nodes: json.nodes, parents, restInverse, restPose});
|
|
@@ -731,9 +752,34 @@ function gltfPart(json, buffers, textures, primitive, matrix, name)
|
|
|
731
752
|
textureRef ? textures[textureRef.index] : undefined, blend || transmission > 0);
|
|
732
753
|
part.pixelated = json.samplers?.[texture?.sampler]?.magFilter === 9728; // NEAREST
|
|
733
754
|
part.unlit = !!material.extensions?.KHR_materials_unlit;
|
|
755
|
+
|
|
756
|
+
// the normal and emissive maps, read at the base color texture's uvs; the emissive texture is multiplied by the
|
|
757
|
+
// factor, which is black by default as the format says, so a texture alone does not glow; a factor with no
|
|
758
|
+
// texture glows all over
|
|
759
|
+
const normalRef = material.normalTexture, emissiveRef = material.emissiveTexture;
|
|
760
|
+
part.normalMap = normalRef && textures[normalRef.index];
|
|
761
|
+
part.normalScale = normalRef?.scale ?? 1;
|
|
762
|
+
const [er, eg, eb] = material.emissiveFactor || [0, 0, 0];
|
|
763
|
+
if (er || eg || eb)
|
|
764
|
+
{
|
|
765
|
+
part.emissiveMap = emissiveRef ? textures[emissiveRef.index] : gltfWhiteTexture();
|
|
766
|
+
part.emissiveMapColor = rgb(gltfSRGB(er), gltfSRGB(eg), gltfSRGB(eb));
|
|
767
|
+
}
|
|
734
768
|
return part;
|
|
735
769
|
}
|
|
736
770
|
|
|
771
|
+
// a 1 by 1 white texture, the emissive map of a material that glows all over with no texture; made once and never
|
|
772
|
+
// freed, like the engine's own white texture, undefined without WebGL
|
|
773
|
+
let gltfWhiteTextureInfo;
|
|
774
|
+
function gltfWhiteTexture()
|
|
775
|
+
{
|
|
776
|
+
if (gltfWhiteTextureInfo || !glContext || typeof OffscreenCanvas == 'undefined') return gltfWhiteTextureInfo;
|
|
777
|
+
const context = createCanvasContext(1);
|
|
778
|
+
context.fillStyle = '#fff';
|
|
779
|
+
context.fillRect(0, 0, 1, 1);
|
|
780
|
+
return gltfWhiteTextureInfo = new TextureInfo(context.canvas);
|
|
781
|
+
}
|
|
782
|
+
|
|
737
783
|
// a glTF color factor or vertex color is linear, where the renderer works in sRGB like the textures, so it is
|
|
738
784
|
// brought across at load or a mid gray material would come out nearly black; 0 and 1 stay exact
|
|
739
785
|
function gltfSRGB(c)
|
package/plugins/math3d.js
CHANGED
|
@@ -652,36 +652,62 @@ class Ray3D
|
|
|
652
652
|
|
|
653
653
|
///////////////////////////////////////////////////////////////////////////////
|
|
654
654
|
// 3D collision helpers, none of them change anything that is passed in
|
|
655
|
-
// Boxes sit centered on pos and take a full size, like drawRect
|
|
655
|
+
// Boxes sit centered on pos and take a full size, like drawRect, upright unless given a rotation, an Euler vec3 like
|
|
656
|
+
// rotation3D, which turns them about their center
|
|
656
657
|
// Cylinders stand up the Y axis, centered on pos, with a full height
|
|
657
658
|
// Names that could be mistaken for 2D functions get a 3D suffix
|
|
658
659
|
|
|
660
|
+
// whether a rotation turns anything, a missing one does not
|
|
661
|
+
const isTurned3D = (rotation)=> !!rotation && !!(rotation.x || rotation.y || rotation.z);
|
|
662
|
+
|
|
663
|
+
// a turned box's three unit axes, the columns of its rotation
|
|
664
|
+
function boxAxes3D(rotation)
|
|
665
|
+
{
|
|
666
|
+
const m = buildMatrix(undefined, rotation).m;
|
|
667
|
+
return [vec3(m[0], m[1], m[2]), vec3(m[4], m[5], m[6]), vec3(m[8], m[9], m[10])];
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// a point in a turned box's own space, from its center, and a vector from that space back to the world's
|
|
671
|
+
function boxLocal3D(point, pos, axes)
|
|
672
|
+
{
|
|
673
|
+
const d = point.subtract(pos);
|
|
674
|
+
return vec3(d.dot(axes[0]), d.dot(axes[1]), d.dot(axes[2]));
|
|
675
|
+
}
|
|
676
|
+
const boxWorld3D = (v, axes)=> axes[0].scale(v.x).add(axes[1].scale(v.y)).add(axes[2].scale(v.z));
|
|
677
|
+
|
|
659
678
|
/**
|
|
660
|
-
* Check if a point is inside
|
|
679
|
+
* Check if a point is inside a box, boundary is inclusive
|
|
661
680
|
* @param {Vector3} point
|
|
662
681
|
* @param {Vector3} pos - Center of the box
|
|
663
682
|
* @param {Vector3} size - Full size of the box
|
|
683
|
+
* @param {Vector3} [rotation] - How the box is turned, upright when left out
|
|
664
684
|
* @return {boolean}
|
|
665
685
|
* @memberof Math3D
|
|
666
686
|
*/
|
|
667
|
-
function isPointInBox3D(point, pos, size)
|
|
687
|
+
function isPointInBox3D(point, pos, size, rotation)
|
|
668
688
|
{
|
|
689
|
+
if (isTurned3D(rotation))
|
|
690
|
+
return isPointInBox3D(boxLocal3D(point, pos, boxAxes3D(rotation)), vec3(), size);
|
|
669
691
|
return abs(point.x - pos.x) <= size.x/2 &&
|
|
670
692
|
abs(point.y - pos.y) <= size.y/2 &&
|
|
671
693
|
abs(point.z - pos.z) <= size.z/2;
|
|
672
694
|
}
|
|
673
695
|
|
|
674
696
|
/**
|
|
675
|
-
* Check if two
|
|
697
|
+
* Check if two boxes are overlapping, touching edges do not overlap
|
|
676
698
|
* @param {Vector3} posA
|
|
677
699
|
* @param {Vector3} sizeA - Full size of box A
|
|
678
700
|
* @param {Vector3} posB
|
|
679
701
|
* @param {Vector3} [sizeB] - Full size of box B, zero for a point
|
|
702
|
+
* @param {Vector3} [rotationA] - How box A is turned, upright when left out
|
|
703
|
+
* @param {Vector3} [rotationB] - How box B is turned
|
|
680
704
|
* @return {boolean}
|
|
681
705
|
* @memberof Math3D
|
|
682
706
|
*/
|
|
683
|
-
function isOverlapping3D(posA, sizeA, posB, sizeB=vec3())
|
|
707
|
+
function isOverlapping3D(posA, sizeA, posB, sizeB=vec3(), rotationA, rotationB)
|
|
684
708
|
{
|
|
709
|
+
if (isTurned3D(rotationA) || isTurned3D(rotationB))
|
|
710
|
+
return !!collideBoxBox3D(posA, sizeA, posB, sizeB, rotationA, rotationB);
|
|
685
711
|
const d = posA.subtract(posB);
|
|
686
712
|
return abs(d.x) < (sizeA.x + sizeB.x)/2 &&
|
|
687
713
|
abs(d.y) < (sizeA.y + sizeB.y)/2 &&
|
|
@@ -710,16 +736,19 @@ function collideSphereSphere(posA, radiusA, posB, radiusB)
|
|
|
710
736
|
}
|
|
711
737
|
|
|
712
738
|
/**
|
|
713
|
-
* Returns the vector to move a sphere out of
|
|
739
|
+
* Returns the vector to move a sphere out of a box, or undefined
|
|
714
740
|
* @param {Vector3} pos - Sphere center
|
|
715
741
|
* @param {number} radius
|
|
716
742
|
* @param {Vector3} boxPos
|
|
717
743
|
* @param {Vector3} boxSize - Full size of the box
|
|
744
|
+
* @param {Vector3} [boxRotation] - How the box is turned, upright when left out
|
|
718
745
|
* @return {Vector3|undefined}
|
|
719
746
|
* @memberof Math3D
|
|
720
747
|
*/
|
|
721
|
-
function collideSphereBox(pos, radius, boxPos, boxSize)
|
|
748
|
+
function collideSphereBox(pos, radius, boxPos, boxSize, boxRotation)
|
|
722
749
|
{
|
|
750
|
+
if (isTurned3D(boxRotation))
|
|
751
|
+
return collideSphereOrientedBox3D(pos, radius, boxPos, boxSize, boxAxes3D(boxRotation));
|
|
723
752
|
const h = boxSize.scale(.5);
|
|
724
753
|
const closest = vec3(
|
|
725
754
|
clamp(pos.x, boxPos.x - h.x, boxPos.x + h.x),
|
|
@@ -807,15 +836,20 @@ function collideSphereCylinder(pos, radius, cylinderPos, cylinderRadius, cylinde
|
|
|
807
836
|
/**
|
|
808
837
|
* Returns the vector to move box A by so it no longer overlaps box B, the shortest way out, or undefined
|
|
809
838
|
* - The 3D twin of collideBoxBox
|
|
839
|
+
* - Turned boxes are tested by the separating axis test, the push is along the direction they overlap least on
|
|
810
840
|
* @param {Vector3} posA
|
|
811
841
|
* @param {Vector3} sizeA - Full size of box A
|
|
812
842
|
* @param {Vector3} posB
|
|
813
843
|
* @param {Vector3} sizeB - Full size of box B
|
|
844
|
+
* @param {Vector3} [rotationA] - How box A is turned, upright when left out
|
|
845
|
+
* @param {Vector3} [rotationB] - How box B is turned
|
|
814
846
|
* @return {Vector3|undefined}
|
|
815
847
|
* @memberof Math3D
|
|
816
848
|
*/
|
|
817
|
-
function collideBoxBox3D(posA, sizeA, posB, sizeB)
|
|
849
|
+
function collideBoxBox3D(posA, sizeA, posB, sizeB, rotationA, rotationB)
|
|
818
850
|
{
|
|
851
|
+
if (isTurned3D(rotationA) || isTurned3D(rotationB))
|
|
852
|
+
return collideOrientedBoxes3D(posA, sizeA, boxAxes3D(rotationA), posB, sizeB, boxAxes3D(rotationB));
|
|
819
853
|
const d = posA.subtract(posB);
|
|
820
854
|
const overlapX = (sizeA.x + sizeB.x)/2 - abs(d.x);
|
|
821
855
|
const overlapY = (sizeA.y + sizeB.y)/2 - abs(d.y);
|
|
@@ -825,6 +859,44 @@ function collideBoxBox3D(posA, sizeA, posB, sizeB)
|
|
|
825
859
|
return pushOutAxis3D(d, overlapX, overlapY, overlapZ);
|
|
826
860
|
}
|
|
827
861
|
|
|
862
|
+
// the push to move a sphere out of a box turned to these axes: in the box's own space it is upright, and the push
|
|
863
|
+
// goes back out turned
|
|
864
|
+
function collideSphereOrientedBox3D(pos, radius, boxPos, boxSize, axes)
|
|
865
|
+
{
|
|
866
|
+
const push = collideSphereBox(boxLocal3D(pos, boxPos, axes), radius, vec3(), boxSize);
|
|
867
|
+
return push && boxWorld3D(push, axes);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// the world's axes, for an upright box among turned ones
|
|
871
|
+
const BOX_WORLD_AXES = Object.freeze([vec3(1, 0, 0), vec3(0, 1, 0), vec3(0, 0, 1)]);
|
|
872
|
+
|
|
873
|
+
// the shortest push to move box A out of box B by the separating axis test: each box's three face directions and
|
|
874
|
+
// the nine across an edge of each; a gap on any of them is no touch, and the push is along the least overlap, from
|
|
875
|
+
// B to A; an edge direction has to overlap clearly less to win, so a box lying on another leaves by the face
|
|
876
|
+
function collideOrientedBoxes3D(posA, sizeA, axesA, posB, sizeB, axesB)
|
|
877
|
+
{
|
|
878
|
+
const d = posA.subtract(posB), ha = sizeA.scale(.5), hb = sizeB.scale(.5);
|
|
879
|
+
const reach = (h, axes, n)=> h.x * abs(axes[0].dot(n)) + h.y * abs(axes[1].dot(n)) + h.z * abs(axes[2].dot(n));
|
|
880
|
+
let push, least = Infinity;
|
|
881
|
+
const separated = (axis, weight)=>
|
|
882
|
+
{
|
|
883
|
+
const lengthSquared = axis.lengthSquared();
|
|
884
|
+
if (lengthSquared < 1e-12) return false; // parallel edges give no direction to test
|
|
885
|
+
const n = axis.scale(lengthSquared ** -.5), along = d.dot(n);
|
|
886
|
+
const overlap = reach(ha, axesA, n) + reach(hb, axesB, n) - abs(along);
|
|
887
|
+
if (overlap <= 0) return true;
|
|
888
|
+
if (overlap * weight < least)
|
|
889
|
+
least = overlap * weight, push = n.scale(along < 0 ? -overlap : overlap);
|
|
890
|
+
return false;
|
|
891
|
+
};
|
|
892
|
+
for (const axis of [...axesA, ...axesB])
|
|
893
|
+
if (separated(axis, 1)) return;
|
|
894
|
+
for (const a of axesA)
|
|
895
|
+
for (const b of axesB)
|
|
896
|
+
if (separated(a.cross(b), 1.05)) return;
|
|
897
|
+
return push;
|
|
898
|
+
}
|
|
899
|
+
|
|
828
900
|
/**
|
|
829
901
|
* Returns the distance along the ray to the first intersection with a sphere, or undefined
|
|
830
902
|
* - The hit is ray.getPosition(distance), a direction that is not unit length scales the distance
|
|
@@ -873,17 +945,25 @@ function raycastPlane(ray, planePos, planeNormal)
|
|
|
873
945
|
}
|
|
874
946
|
|
|
875
947
|
/**
|
|
876
|
-
* Returns the distance along the ray to the first intersection with
|
|
948
|
+
* Returns the distance along the ray to the first intersection with a box, or undefined
|
|
877
949
|
* - The hit is ray.getPosition(distance), a direction that is not unit length scales the distance
|
|
878
950
|
* - A ray starting inside the box is already there, so it gets back 0
|
|
879
951
|
* @param {Ray3D} ray
|
|
880
952
|
* @param {Vector3} pos - Center of the box
|
|
881
953
|
* @param {Vector3} size - Full size of the box
|
|
954
|
+
* @param {Vector3} [rotation] - How the box is turned, upright when left out
|
|
882
955
|
* @return {number|undefined}
|
|
883
956
|
* @memberof Math3D
|
|
884
957
|
*/
|
|
885
|
-
function raycastBox(ray, pos, size)
|
|
958
|
+
function raycastBox(ray, pos, size, rotation)
|
|
886
959
|
{
|
|
960
|
+
if (isTurned3D(rotation))
|
|
961
|
+
{
|
|
962
|
+
// the ray in the box's own space, where the box is upright; turning keeps the distances
|
|
963
|
+
const axes = boxAxes3D(rotation), d = ray.direction;
|
|
964
|
+
const direction = vec3(d.dot(axes[0]), d.dot(axes[1]), d.dot(axes[2]));
|
|
965
|
+
return raycastBox(new Ray3D(boxLocal3D(ray.origin, pos, axes), direction), vec3(), size);
|
|
966
|
+
}
|
|
887
967
|
const {origin, direction} = ray;
|
|
888
968
|
const h = size.scale(.5);
|
|
889
969
|
const boxMin = pos.subtract(h), boxMax = pos.add(h);
|
package/plugins/pluginExport.js
CHANGED
|
@@ -103,6 +103,10 @@ export
|
|
|
103
103
|
tweenUpdate,
|
|
104
104
|
Ease,
|
|
105
105
|
|
|
106
|
+
// Scene System
|
|
107
|
+
setScene,
|
|
108
|
+
getScene,
|
|
109
|
+
|
|
106
110
|
// Path Finding
|
|
107
111
|
PathFinder,
|
|
108
112
|
PathFinderNode,
|
|
@@ -151,6 +155,7 @@ export
|
|
|
151
155
|
buildRibbon,
|
|
152
156
|
buildLoft,
|
|
153
157
|
buildSky,
|
|
158
|
+
normalMapFromHeight,
|
|
154
159
|
buildExtrude,
|
|
155
160
|
buildText3D,
|
|
156
161
|
HeightMap,
|
|
@@ -177,6 +182,11 @@ export
|
|
|
177
182
|
debugLine3D,
|
|
178
183
|
debugPoint3D,
|
|
179
184
|
|
|
185
|
+
// 3D Level
|
|
186
|
+
level3DAddType,
|
|
187
|
+
level3DAddMesh,
|
|
188
|
+
level3DLoad,
|
|
189
|
+
|
|
180
190
|
// Three.js
|
|
181
191
|
threeJS,
|
|
182
192
|
ThreeJSPlugin,
|
package/plugins/postProcess.js
CHANGED
|
@@ -93,18 +93,7 @@ class PostProcessPlugin
|
|
|
93
93
|
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
94
94
|
'}' // end of shader
|
|
95
95
|
,
|
|
96
|
-
|
|
97
|
-
'precision highp float;'+ // use highp for accuracy
|
|
98
|
-
'uniform sampler2D iChannel0;'+ // input texture
|
|
99
|
-
'uniform sampler2D iChannel1;'+ // the previous frame's output, when feedbackTexture is set
|
|
100
|
-
'uniform vec3 iResolution;'+ // size of output texture
|
|
101
|
-
'uniform float iTime;'+ // time
|
|
102
|
-
'out vec4 c;'+ // out color
|
|
103
|
-
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
104
|
-
'void main(){'+ // shader entry point
|
|
105
|
-
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
106
|
-
'c.a=1.;'+ // always use full alpha
|
|
107
|
-
'}' // end of shader
|
|
96
|
+
postProcessFragmentSource(shaderCode)
|
|
108
97
|
);
|
|
109
98
|
|
|
110
99
|
// setup VAO for post processing
|
|
@@ -192,10 +181,20 @@ class PostProcessPlugin
|
|
|
192
181
|
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.feedbackTexture);
|
|
193
182
|
}
|
|
194
183
|
|
|
184
|
+
// the 3D depth, when render3D draws it, on the third texture unit, with what sceneDepth needs to read it
|
|
185
|
+
const depth = typeof render3D != 'undefined' && render3D?.depthTexture ? render3D : undefined;
|
|
186
|
+
glContext.activeTexture(glContext.TEXTURE2);
|
|
187
|
+
glContext.bindTexture(glContext.TEXTURE_2D, depth?.cameraDepthTexture || null);
|
|
188
|
+
glContext.activeTexture(glContext.TEXTURE0);
|
|
189
|
+
const camera = depth?.camera;
|
|
190
|
+
camera && glContext.uniform3f(glUniformLocation(postProcess.shader, 'iDepthRange'), camera.near,
|
|
191
|
+
camera.far == Infinity ? 0 : camera.far, camera.orthographic ? 1 : 0);
|
|
192
|
+
|
|
195
193
|
// set uniforms and draw
|
|
196
194
|
const uniformLocation = (name)=>glUniformLocation(postProcess.shader, name);
|
|
197
195
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
198
196
|
glContext.uniform1i(uniformLocation('iChannel1'), 1);
|
|
197
|
+
glContext.uniform1i(uniformLocation('iChannel2'), 2);
|
|
199
198
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
200
199
|
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
201
200
|
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
|
|
@@ -207,6 +206,11 @@ class PostProcessPlugin
|
|
|
207
206
|
glContext.activeTexture(glContext.TEXTURE0);
|
|
208
207
|
}
|
|
209
208
|
|
|
209
|
+
// the depth leaves the third unit for whatever draws next
|
|
210
|
+
glContext.activeTexture(glContext.TEXTURE2);
|
|
211
|
+
glContext.bindTexture(glContext.TEXTURE_2D, null);
|
|
212
|
+
glContext.activeTexture(glContext.TEXTURE0);
|
|
213
|
+
|
|
210
214
|
// restore defaults so subsequent dynamic texture uploads aren't flipped or premultiplied
|
|
211
215
|
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, false);
|
|
212
216
|
glContext.pixelStorei(glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
@@ -223,6 +227,31 @@ class PostProcessPlugin
|
|
|
223
227
|
|
|
224
228
|
///////////////////////////////////////////////////////////////////////////////
|
|
225
229
|
|
|
230
|
+
// the fragment shader of the post process pass around its mainImage snippet: the frame on iChannel0, the last output
|
|
231
|
+
// on iChannel1 with a feedback texture, and the 3D depth on iChannel2 when render3D.depthTexture is on, read with
|
|
232
|
+
// sceneDepth(uv), the distance from the camera along its view in world units, uv 0 to 1 across the screen
|
|
233
|
+
function postProcessFragmentSource(shaderCode)
|
|
234
|
+
{
|
|
235
|
+
return '#version 300 es\n' + // specify GLSL ES version
|
|
236
|
+
'precision highp float;'+ // use highp for accuracy
|
|
237
|
+
'uniform sampler2D iChannel0;'+ // input texture
|
|
238
|
+
'uniform sampler2D iChannel1;'+ // the previous frame's output, when feedbackTexture is set
|
|
239
|
+
'uniform sampler2D iChannel2;'+ // the 3D depth, when render3D.depthTexture is on
|
|
240
|
+
'uniform vec3 iResolution;'+ // size of output texture
|
|
241
|
+
'uniform float iTime;'+ // time
|
|
242
|
+
'uniform vec3 iDepthRange;'+ // the camera's near, its far or 0 for none, and 1 when orthographic
|
|
243
|
+
'out vec4 c;'+ // out color
|
|
244
|
+
// the depth texture's value back to a distance, as the camera's projection put it there
|
|
245
|
+
'float sceneDepth(vec2 uv){'+
|
|
246
|
+
'float d=texture(iChannel2,uv).r*2.-1.,n=iDepthRange.x,f=iDepthRange.y;'+
|
|
247
|
+
'return iDepthRange.z>0.?(d*(f-n)+f+n)/2.:f>0.?2.*n*f/(f+n-d*(f-n)):2.*n/(1.-d);}'+
|
|
248
|
+
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
249
|
+
'void main(){'+ // shader entry point
|
|
250
|
+
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
251
|
+
'c.a=1.;'+ // always use full alpha
|
|
252
|
+
'}'; // end of shader
|
|
253
|
+
}
|
|
254
|
+
|
|
226
255
|
/**
|
|
227
256
|
* Shader code for a bloom effect, the bright parts of the image blurred back over it
|
|
228
257
|
* - Pass it to PostProcessPlugin, or edit the string to build an effect on top of it
|