diff --git a/src/core.h b/src/core.h index 76fb23f..b9e7faa 100644 --- a/src/core.h +++ b/src/core.h @@ -139,9 +139,9 @@ namespace Core { vec4 lightColor[MAX_LIGHTS]; vec4 color; - enum { passOpaque, passShadow } pass; + enum Pass { passCompose, passShadow, passAmbient } pass; - GLuint RT; + GLuint RT, RB; struct { Shader *shader; @@ -246,6 +246,10 @@ namespace Core { // support.depthTexture = support.shadowSampler = false; // depth textures is not supported in Safari browser in fact :( glGenFramebuffers(1, &RT); + glGenRenderbuffers(1, &RB); + glBindRenderbuffer(GL_RENDERBUFFER, RB); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, 64, 64); + glBindRenderbuffer(GL_RENDERBUFFER, 0); Sound::init(); @@ -258,6 +262,11 @@ namespace Core { Sound::free(); } + void resetStates() { + memset(&active, 0, sizeof(active)); + glEnable(GL_DEPTH_TEST); + } + void clear(const vec4 &color) { glClearColor(color.x, color.y, color.z, color.w); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); @@ -316,6 +325,8 @@ namespace Core { glBindFramebuffer(GL_FRAMEBUFFER, RT); glFramebufferTexture2D(GL_FRAMEBUFFER, target->depth ? GL_DEPTH_ATTACHMENT : GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target->ID, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, target->depth ? GL_COLOR_ATTACHMENT0 : GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, target->dummy ? target->dummy->ID : 0, 0); + if (!target->depth) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, RB); bool mask = !target->depth; glColorMask(mask, mask, mask, mask); diff --git a/src/lara.h b/src/lara.h index fc94202..81e4d96 100644 --- a/src/lara.h +++ b/src/lara.h @@ -1559,20 +1559,42 @@ struct Lara : Character { } void move() { + TR::Entity &e = getEntity(); + TR::Level::FloorInfo info; + + float f, c; + bool canPassGap = true; + /* + if (velocity != 0.0f) { + vec3 dir = velocity.normal() * 128.0f; + vec3 p = pos + dir; + level->getFloorInfo(e.room, (int)p.x, (int)p.y, (int)p.z, info); + if (info.floor < p.y - (256 + 128) || info.ceiling > p.y - 768) { // wall + vec3 axis = dir.axisXZ(); + vec3 normal = (p - vec3(int(p.x / 1024.0f) * 1024.0f + 512.0f, p.y, int(p.z / 1024.0f) * 1024.0f + 512.0f)).axisXZ(); + LOG("%f %f = %f %f = %f\n", axis.x, axis.z, normal.x, normal.z, abs(axis.dot(normal))); + if (abs(axis.dot(normal)) > EPS) { + canPassGap = false; + } else { + updateEntity(); + checkRoom(); + return; + } + } + } + */ vec3 offset = velocity * Core::deltaTime * 30.0f; vec3 p = pos; pos = pos + offset; - TR::Entity &e = getEntity(); - TR::Level::FloorInfo info; - level->getFloorInfo(e.room, (int)pos.x, (int)pos.y, (int)pos.z, info); - - // get frame to get height - bool canPassGap = (info.floor - info.ceiling) >= (stand == STAND_GROUND ? 768 : 512); - float f = info.floor - pos.y; - float c = pos.y - info.ceiling; + if (canPassGap) { + level->getFloorInfo(e.room, (int)pos.x, (int)pos.y, (int)pos.z, info); + canPassGap = (info.floor - info.ceiling) >= (stand == STAND_GROUND ? 768 : 512); + } + f = info.floor - pos.y; + c = pos.y - info.ceiling; /* TR::Animation *anim = animation; Box eBox = Box(pos - vec3(128.0f, 0.0f, 128.0f), pos + vec3(128.0, getHeight(), 128.0f)); // getBoundingBox(); diff --git a/src/level.h b/src/level.h index f6b05f4..089e603 100644 --- a/src/level.h +++ b/src/level.h @@ -22,7 +22,7 @@ const char GUI[] = ; struct Level { - enum { shDefault, shShadow, shGUI, shMAX }; + enum { shCompose, shShadow, shAmbient, shGUI, shMAX }; TR::Level level; Shader *shaders[shMAX]; @@ -32,6 +32,7 @@ struct Level { Lara *lara; Camera *camera; Texture *shadow; + Texture *ambient[4]; // 64, 16, 4, 1 float time; @@ -42,6 +43,8 @@ struct Level { mesh = new MeshBuilder(level); shadow = new Texture(1024, 1024, true); + for (int i = 0; i < 4; i++) + ambient[i] = new Texture(64 >> (i << 1), 64 >> (i << 1), false); initAtlas(); initShaders(); @@ -154,6 +157,9 @@ struct Level { delete shaders[i]; delete shadow; + for (int i = 0; i < 4; i++) + delete ambient[i]; + delete atlas; delete mesh; @@ -214,11 +220,12 @@ struct Level { else strcat(ext, "#define SHADOW_COLOR\n"); - sprintf(def, "%s#define MAX_LIGHTS %d\n#define MAX_RANGES %d\n#define MAX_OFFSETS %d\n#define MAX_SHADOW_DIST %d.0\n", ext, MAX_LIGHTS, mesh->animTexRangesCount, mesh->animTexOffsetsCount, MAX_SHADOW_DIST); - shaders[shDefault] = new Shader(SHADER, def); + sprintf(def, "%s#define PASS_COMPOSE\n#define MAX_LIGHTS %d\n#define MAX_RANGES %d\n#define MAX_OFFSETS %d\n#define MAX_SHADOW_DIST %d.0\n", ext, MAX_LIGHTS, mesh->animTexRangesCount, mesh->animTexOffsetsCount, MAX_SHADOW_DIST); + shaders[shCompose] = new Shader(SHADER, def); sprintf(def, "%s#define PASS_SHADOW\n", ext); shaders[shShadow] = new Shader(SHADER, def); - sprintf(ext, "%s#define SPRITE\n", def); + sprintf(def, "%s#define PASS_AMBIENT\n", ext); + shaders[shAmbient] = new Shader(SHADER, def); shaders[shGUI] = new Shader(GUI, ""); } @@ -323,13 +330,10 @@ struct Level { } #endif - void setRoomShader(const TR::Room &room, float intensity) { - if (Core::pass == Core::passShadow) { - shaders[shShadow]->bind(); + void setRoomParams(const TR::Room &room, float intensity) { + if (Core::pass == Core::passShadow) return; - } - shaders[shDefault]->bind(); if (room.flags.water) { Core::color = vec4(0.6f, 0.9f, 0.9f, intensity); Core::active.shader->setParam(uCaustics, 1); @@ -347,7 +351,7 @@ struct Level { TR::Room &room = level.rooms[roomIndex]; vec3 offset = vec3(float(room.info.x), 0.0f, float(room.info.z)); - setRoomShader(room, intensityf(room.ambient)); + setRoomParams(room, intensityf(room.ambient)); Shader *sh = Core::active.shader; Core::lightColor[0] = vec4(0, 0, 0, 1); @@ -396,7 +400,7 @@ struct Level { sh->setParam(uModel, Core::mModel); // render room geometry - if (Core::pass == Core::passOpaque) { + if (Core::pass == Core::passCompose || Core::pass == Core::passAmbient) { mesh->renderRoomGeometry(roomIndex); } @@ -493,7 +497,7 @@ struct Level { return; int16 lum = entity.intensity == -1 ? room.ambient : entity.intensity; - setRoomShader(room, intensityf(lum)); + setRoomParams(room, intensityf(lum)); if (entity.modelIndex > 0) { // model getLight(((Controller*)entity.controller)->pos, entity.room); @@ -512,7 +516,6 @@ struct Level { ((Controller*)entity.controller)->render(camera->frustum, mesh); } - void update() { time += Core::deltaTime; @@ -529,7 +532,7 @@ struct Level { void setup() { PROFILE_MARKER("SETUP"); - camera->setup(Core::pass != Core::passShadow);; + camera->setup(Core::pass == Core::passCompose); atlas->bind(0); @@ -537,19 +540,16 @@ struct Level { mesh->bind(); // set frame constants for all shaders - Core::active.shader = NULL; - for (int i = 0; i < shMAX; i++) { - shaders[i]->bind(); - shaders[i]->setParam(uViewProj, Core::mViewProj); - shaders[i]->setParam(uLightProj, Core::mLightProj); - shaders[i]->setParam(uViewInv, Core::mViewInv); - shaders[i]->setParam(uViewPos, Core::viewPos); - shaders[i]->setParam(uTime, time); - shaders[i]->setParam(uLightTarget, lara->pos); - shaders[i]->setParam(uAnimTexRanges, mesh->animTexRanges[0], mesh->animTexRangesCount); - shaders[i]->setParam(uAnimTexOffsets, mesh->animTexOffsets[0], mesh->animTexOffsetsCount); - } - glEnable(GL_DEPTH_TEST); + Shader *sh = Core::active.shader; + sh->bind(); + sh->setParam(uViewProj, Core::mViewProj); + sh->setParam(uLightProj, Core::mLightProj); + sh->setParam(uViewInv, Core::mViewInv); + sh->setParam(uViewPos, Core::viewPos); + sh->setParam(uTime, time); + sh->setParam(uLightTarget, lara->pos); + sh->setParam(uAnimTexRanges, mesh->animTexRanges[0], mesh->animTexRangesCount); + sh->setParam(uAnimTexOffsets, mesh->animTexOffsets[0], mesh->animTexOffsetsCount); Core::mModel.identity(); @@ -562,32 +562,50 @@ struct Level { room.meshes[j].flags.rendered = false; // clear visible flag for room static meshes } - for (int i = 0; i < level.entitiesCount; i++) - level.entities[i].flags.rendered = false; + if (Core::pass != Core::passAmbient) + for (int i = 0; i < level.entitiesCount; i++) + level.entities[i].flags.rendered = false; } - void renderRooms() { + void renderRooms(int roomIndex) { PROFILE_MARKER("ROOMS"); #ifdef LEVEL_EDITOR for (int i = 0; i < level.roomsCount; i++) renderRoom(i); #else - renderRoom(camera->getRoomIndex()); + renderRoom(roomIndex); #endif } void renderEntities() { PROFILE_MARKER("ENTITIES"); - shaders[Core::pass == Core::passShadow ? shShadow : shDefault]->bind(); for (int i = 0; i < level.entitiesCount; i++) renderEntity(level.entities[i]); } - void renderScene() { + void renderScene(int roomIndex) { PROFILE_MARKER("SCENE"); setup(); - renderRooms(); - renderEntities(); + renderRooms(roomIndex); + if (Core::pass != Core::passAmbient) + renderEntities(); + } + + void setupCubeCamera(const vec3 &pos, int face) { + vec3 up = vec3(0, 1, 0); + vec3 dir; + switch (face) { + case 0 : dir = vec3( 1, 0, 0); break; + case 1 : dir = vec3(-1, 0, 0); break; + case 2 : dir = vec3( 0, 1, 0); up = vec3(1, 0, 0); break; + case 3 : dir = vec3( 0, -1, 0); up = vec3(1, 0, 0); break; + case 4 : dir = vec3( 0, 0, 1); break; + case 5 : dir = vec3( 0, 0, -1); break; + } + + Core::mViewInv = mat4(pos, pos + dir, up); + Core::mView = Core::mViewInv.inverse(); + Core::mProj = mat4(90, 1.0f, camera->znear, camera->zfar); } bool setupLightCamera() { @@ -600,48 +618,76 @@ struct Level { TR::Room::Light &light = level.rooms[room].lights[idx]; vec3 shadowLightPos = vec3(float(light.x), float(light.y), float(light.z)); - Core::mView = mat4(shadowLightPos, pos - vec3(0, 256, 0), vec3(0, -1, 0)).inverse(); - Core::mProj = mat4(120, 1.0f, camera->znear, camera->zfar); - - Core::mViewProj = Core::mProj * Core::mView; + Core::mViewInv = mat4(shadowLightPos, pos - vec3(0, 256, 0), vec3(0, -1, 0)); + Core::mView = Core::mViewInv.inverse(); + Core::mProj = mat4(120, 1.0f, camera->znear, camera->zfar); mat4 bias; bias.identity(); bias.e03 = bias.e13 = bias.e23 = bias.e00 = bias.e11 = bias.e22 = 0.5f; - Core::mLightProj = bias * Core::mProj * Core::mView; // * ( * ) ? - - Shader *sh = shaders[shShadow]; - sh->bind(); - sh->setParam(uViewProj, Core::mViewProj); - sh->setParam(uLightProj, Core::mLightProj); + Core::mLightProj = bias * Core::mProj * Core::mView; return true; } - void renderShadows() { - if (!setupLightCamera()) return; + void setPassShader(Core::Pass pass) { + Core::pass = pass; + Shader *sh = NULL; + switch (pass) { + case Core::passCompose : sh = shaders[shCompose]; break; + case Core::passShadow : sh = shaders[shShadow]; break; + case Core::passAmbient : sh = shaders[shAmbient]; break; + } + ASSERT(sh); + sh->bind(); + } + + void renderAmbient(int roomIndex, const vec3 &pos, vec3 *cube) { + PROFILE_MARKER("PASS_AMBIENT"); + setupCubeCamera(pos, 4); + + setPassShader(Core::passAmbient); + Core::setBlending(bmAlpha); + Core::setTarget(ambient[0]); + Core::setViewport(0, 0, ambient[0]->width, ambient[0]->height); + Core::clear(vec4(0, 0, 0, 1)); + renderScene(roomIndex); + Core::setTarget(NULL); + } + + void renderShadows(int roomIndex) { PROFILE_MARKER("PASS_SHADOW"); + if (!setupLightCamera()) return; Texture::unbind(sShadow); - Core::pass = Core::passShadow; - Core::setBlending(bmNone); + setPassShader(Core::passShadow); + Core::setBlending(bmNone); Core::setTarget(shadow); Core::setViewport(0, 0, shadow->width, shadow->height); Core::clear(vec4(1.0)); Core::setCulling(cfBack); - renderScene(); + renderScene(roomIndex); Core::setCulling(cfFront); Core::setTarget(NULL); - shadow->bind(sShadow); } - - void render() { - renderShadows(); - - Core::pass = Core::passOpaque; + + void renderCompose(int roomIndex) { + PROFILE_MARKER("PASS_COMPOSE"); + setPassShader(Core::passCompose); + Core::setBlending(bmAlpha); Core::clear(vec4(0.0f)); Core::setViewport(0, 0, Core::width, Core::height); - renderScene(); + shadow->bind(sShadow); + renderScene(roomIndex); + } + + void render() { + Core::resetStates(); + + vec3 cube[6]; + // renderAmbient(lara->getRoomIndex(), lara->pos - vec3(0, 512, 0), cube); + renderShadows(lara->getRoomIndex()); + renderCompose(camera->getRoomIndex()); #ifdef _DEBUG static int modelIndex = 0; @@ -673,29 +719,29 @@ struct Level { Debug::begin(); // Debug::Level::rooms(level, lara->pos, lara->getEntity().room); - Debug::Level::lights(level, lara->getRoomIndex()); + // Debug::Level::lights(level, lara->getRoomIndex()); // Debug::Level::sectors(level, lara->getRoomIndex(), (int)lara->pos.y); // Debug::Level::portals(level); // Debug::Level::meshes(level); // Debug::Level::entities(level); - /* + glPushMatrix(); glColor3f(1, 1, 1); glTranslatef(lara->pos.x, lara->pos.y, lara->pos.z); Texture::unbind(sShadow); - shadow->bind(sDiffuse); + ambient[0]->bind(sDiffuse); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBegin(GL_QUADS); - glTexCoord2f(0, 0); glVertex3f( 0, 0, 0); - glTexCoord2f(1, 0); glVertex3f(1024, 0, 0); - glTexCoord2f(1, 1); glVertex3f(1024, -1024, 0); - glTexCoord2f(0, 1); glVertex3f( 0, -1024, 0); + glTexCoord2f(1, 1); glVertex3f( 0, 0, 0); + glTexCoord2f(0, 1); glVertex3f(1024, 0, 0); + glTexCoord2f(0, 0); glVertex3f(1024, -1024, 0); + glTexCoord2f(1, 0); glVertex3f( 0, -1024, 0); glEnd(); glPopMatrix(); glDisable(GL_CULL_FACE); glDisable(GL_TEXTURE_2D); - */ + /* shaders[shGUI]->bind(); Core::mViewProj = mat4(0, (float)Core::width, (float)Core::height, 0, 0, 1); diff --git a/src/shader.glsl b/src/shader.glsl index 0677d7f..d2a51d3 100644 --- a/src/shader.glsl +++ b/src/shader.glsl @@ -2,12 +2,14 @@ R"====( varying vec2 vTexCoord; #ifndef PASS_SHADOW - varying vec3 vCoord; - varying vec4 vNormal; - varying vec3 vViewVec; + #ifndef PASS_AMBIENT + varying vec3 vCoord; + varying vec4 vNormal; + varying vec3 vViewVec; + varying vec4 vLightProj; + varying float vLightTargetDist; + #endif varying vec4 vColor; - varying vec4 vLightProj; - varying float vLightTargetDist; #endif #define TYPE_SPRITE 0 @@ -16,26 +18,34 @@ varying vec2 vTexCoord; #define TYPE_FLASH 3 uniform int uType; -uniform int uCaustics; -uniform float uTime; + +#ifdef PASS_COMPOSE + uniform int uCaustics; + uniform float uTime; +#endif #ifdef VERTEX uniform mat4 uViewProj; uniform mat4 uModel; - uniform mat4 uViewInv; - uniform mat4 uLightProj; - uniform vec3 uLightTarget; - #ifndef PASS_SHADOW + #ifndef PASS_AMBIENT + uniform mat4 uViewInv; + uniform mat4 uLightProj; + uniform vec3 uLightTarget; + #endif + + #ifdef PASS_COMPOSE uniform vec3 uViewPos; uniform vec2 uAnimTexRanges[MAX_RANGES]; uniform vec2 uAnimTexOffsets[MAX_OFFSETS]; #endif - attribute vec4 aCoord; attribute vec4 aTexCoord; - attribute vec4 aNormal; + + #ifndef PASS_AMBIENT + attribute vec4 aNormal; + #endif #ifndef PASS_SHADOW attribute vec4 aColor; @@ -45,32 +55,31 @@ uniform float uTime; void main() { vec4 coord = uModel * vec4(aCoord.xyz, 1.0); - - if (uType != TYPE_SPRITE) { - #ifndef PASS_SHADOW + #ifdef PASS_COMPOSE + if (uType != TYPE_SPRITE) { // animated texture coordinates vec2 range = uAnimTexRanges[int(aTexCoord.z)]; // x - start index, y - count float f = fract((aTexCoord.w + uTime * 4.0 - range.x) / range.y) * range.y; vec2 offset = uAnimTexOffsets[int(range.x + f)]; // texCoord offset from first frame - vTexCoord = (aTexCoord.xy + offset) * TEXCOORD_SCALE; // first frame + offset * isAnimated - vNormal = vec4((uModel * vec4(aNormal.xyz, 0.0)).xyz, aNormal.w); - #else - vTexCoord = aTexCoord.xy * TEXCOORD_SCALE; - #endif - } else { - coord.xyz += uViewInv[0].xyz * aTexCoord.z - uViewInv[1].xyz * aTexCoord.w; - #ifndef PASS_SHADOW - vTexCoord = aTexCoord.xy * TEXCOORD_SCALE; - vNormal = vec4(uViewPos.xyz - coord.xyz, 0.0); - #endif - } + vTexCoord = (aTexCoord.xy + offset) * TEXCOORD_SCALE; // first frame + offset * isAnimated + vNormal = vec4((uModel * vec4(aNormal.xyz, 0.0)).xyz, aNormal.w); + } else { + coord.xyz += uViewInv[0].xyz * aTexCoord.z - uViewInv[1].xyz * aTexCoord.w; + vTexCoord = aTexCoord.xy * TEXCOORD_SCALE; + vNormal = vec4(uViewPos.xyz - coord.xyz, 0.0); + } + #else + vTexCoord = aTexCoord.xy * TEXCOORD_SCALE; + #endif #ifndef PASS_SHADOW vColor = aColor; + #endif + #ifdef PASS_COMPOSE if (uCaustics != 0) { float sum = coord.x + coord.y + coord.z; vColor.xyz *= abs(sin(sum / 512.0 + uTime)) * 1.5 + 0.5; // color dodge @@ -91,7 +100,7 @@ uniform float uTime; #else uniform sampler2D sDiffuse; uniform vec4 uColor; - #ifndef PASS_SHADOW + #ifdef PASS_COMPOSE uniform vec3 uLightPos[MAX_LIGHTS]; uniform vec4 uLightColor[MAX_LIGHTS]; #endif @@ -106,7 +115,9 @@ uniform float uTime; return res; } #endif - #else + #endif + + #ifdef PASS_COMPOSE #ifdef AMBIENT_CUBE uniform vec3 uAmbientCube[6]; @@ -180,7 +191,7 @@ uniform float uTime; poissonDisk[13] = vec2( -0.81409955, 0.91437590 ); poissonDisk[14] = vec2( 0.19984126, 0.78641367 ); poissonDisk[15] = vec2( 0.14383161, -0.14100790 ); -//return SHADOW(p); + float rShadow = 0.0; for (int i = 0; i < 16; i += 1) rShadow += SHADOW(p + vec3(poissonDisk[i] * 1.5, 0.0) * (1.0 / 1024.0)); @@ -215,34 +226,38 @@ uniform float uTime; color.xyz = pow(abs(color.xyz), vec3(2.2)); // to linear space - // calc point lights - if (uType != TYPE_FLASH) { - vec3 normal = normalize(vNormal.xyz); - vec3 viewVec = normalize(vViewVec); - vec3 light = vec3(0.0); + #ifdef PASS_AMBIENT + color.xyz *= vColor.w; + #else + // calc point lights + if (uType != TYPE_FLASH) { + vec3 normal = normalize(vNormal.xyz); + vec3 viewVec = normalize(vViewVec); + vec3 light = vec3(0.0); - for (int i = 1; i < MAX_LIGHTS; i++) // additional lights - light += calcLight(normal, uLightPos[i], uLightColor[i]); + for (int i = 1; i < MAX_LIGHTS; i++) // additional lights + light += calcLight(normal, uLightPos[i], uLightColor[i]); - // apply lighting - if (uType == TYPE_SPRITE) { - light += vColor.w * uColor.w; + // apply lighting + if (uType == TYPE_SPRITE) { + light += vColor.w * uColor.w; + } + + if (uType == TYPE_ROOM) { + light += mix(min(uColor.w, vColor.w), vColor.w, getShadow(vLightProj)); + } + + if (uType == TYPE_ENTITY) { + float shadow = getShadow(vLightProj); + vec3 lum = calcLight(normal, uLightPos[0], uLightColor[0]) * shadow + uColor.w; + light += lum; + light *= dot(normal, viewVec) * 0.2 + 0.8; // apply backlight + } + color.xyz *= light; + } else { + color.w = uColor.w; } - - if (uType == TYPE_ROOM) { - light += mix(min(uColor.w, vColor.w), vColor.w, getShadow(vLightProj)); - } - - if (uType == TYPE_ENTITY) { - float shadow = getShadow(vLightProj); - vec3 lum = calcLight(normal, uLightPos[0], uLightColor[0]) * shadow + uColor.w; - light += lum; - light *= dot(normal, viewVec) * 0.2 + 0.8; // apply backlight - } - color.xyz *= light; - } else { - color.w = uColor.w; - } + #endif color.xyz = pow(abs(color.xyz), vec3(1.0/2.2)); // back to gamma space diff --git a/src/utils.h b/src/utils.h index 74b586e..80b81ed 100644 --- a/src/utils.h +++ b/src/utils.h @@ -133,6 +133,7 @@ struct vec3 { float length2() const { return dot(*this); } float length() const { return sqrtf(length2()); } vec3 normal() const { float s = length(); return s == 0.0f ? (*this) : (*this)*(1.0f/s); } + vec3 axisXZ() const { return (fabsf(x) > fabsf(z)) ? vec3(sign(x), 0, 0) : vec3(0, 0, sign(z)); } vec3 lerp(const vec3 &v, const float t) const { if (t <= 0.0f) return *this;