Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
@ -0,0 +1,166 @@ |
|||
#include "./DNSServer.h" |
|||
#include <lwip/def.h> |
|||
#include <Arduino.h> |
|||
|
|||
#define DEBUG |
|||
#define DEBUG_OUTPUT Serial |
|||
|
|||
DNSServer::DNSServer() |
|||
{ |
|||
_ttl = htonl(60); |
|||
_errorReplyCode = DNSReplyCode::NonExistentDomain; |
|||
} |
|||
|
|||
bool DNSServer::start(const uint16_t &port, const String &domainName, |
|||
const IPAddress &resolvedIP) |
|||
{ |
|||
_port = port; |
|||
_domainName = domainName; |
|||
_resolvedIP[0] = resolvedIP[0]; |
|||
_resolvedIP[1] = resolvedIP[1]; |
|||
_resolvedIP[2] = resolvedIP[2]; |
|||
_resolvedIP[3] = resolvedIP[3]; |
|||
downcaseAndRemoveWwwPrefix(_domainName); |
|||
return _udp.begin(_port) == 1; |
|||
} |
|||
|
|||
void DNSServer::setErrorReplyCode(const DNSReplyCode &replyCode) |
|||
{ |
|||
_errorReplyCode = replyCode; |
|||
} |
|||
|
|||
void DNSServer::setTTL(const uint32_t &ttl) |
|||
{ |
|||
_ttl = htonl(ttl); |
|||
} |
|||
|
|||
void DNSServer::stop() |
|||
{ |
|||
_udp.stop(); |
|||
} |
|||
|
|||
void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName) |
|||
{ |
|||
domainName.toLowerCase(); |
|||
domainName.replace("www.", ""); |
|||
domainName.replace("https://", ""); |
|||
} |
|||
|
|||
void DNSServer::processNextRequest() |
|||
{ |
|||
_currentPacketSize = _udp.parsePacket(); |
|||
if (_currentPacketSize) |
|||
{ |
|||
_buffer = (unsigned char*)malloc(_currentPacketSize * sizeof(char)); |
|||
_udp.read(_buffer, _currentPacketSize); |
|||
_dnsHeader = (DNSHeader*) _buffer; |
|||
|
|||
if (_dnsHeader->QR == DNS_QR_QUERY && |
|||
_dnsHeader->OPCode == DNS_OPCODE_QUERY && |
|||
requestIncludesOnlyOneQuestion() && |
|||
(_domainName == "*" || getDomainNameWithoutWwwPrefix() == _domainName) |
|||
) |
|||
|
|||
{ |
|||
replyWithIP(); |
|||
} |
|||
else if (_dnsHeader->QR == DNS_QR_QUERY) |
|||
{ |
|||
replyWithCustomCode(); |
|||
} |
|||
|
|||
free(_buffer); |
|||
} |
|||
} |
|||
|
|||
bool DNSServer::requestIncludesOnlyOneQuestion() |
|||
{ |
|||
return ntohs(_dnsHeader->QDCount) == 1 && |
|||
_dnsHeader->ANCount == 0 && |
|||
_dnsHeader->NSCount == 0 && |
|||
_dnsHeader->ARCount == 0; |
|||
} |
|||
|
|||
String DNSServer::getDomainNameWithoutWwwPrefix() |
|||
{ |
|||
String parsedDomainName = ""; |
|||
unsigned char *start = _buffer + 12; |
|||
if (*start == 0) |
|||
{ |
|||
return parsedDomainName; |
|||
} |
|||
int pos = 0; |
|||
while(true) |
|||
{ |
|||
unsigned char labelLength = *(start + pos); |
|||
for(int i = 0; i < labelLength; i++) |
|||
{ |
|||
pos++; |
|||
parsedDomainName += (char)*(start + pos); |
|||
} |
|||
pos++; |
|||
if (*(start + pos) == 0) |
|||
{ |
|||
downcaseAndRemoveWwwPrefix(parsedDomainName); |
|||
return parsedDomainName; |
|||
} |
|||
else |
|||
{ |
|||
parsedDomainName += "."; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void DNSServer::replyWithIP() |
|||
{ |
|||
_dnsHeader->QR = DNS_QR_RESPONSE; |
|||
_dnsHeader->ANCount = _dnsHeader->QDCount; |
|||
_dnsHeader->QDCount = _dnsHeader->QDCount; |
|||
//_dnsHeader->RA = 1;
|
|||
|
|||
_udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); |
|||
_udp.write(_buffer, _currentPacketSize); |
|||
|
|||
_udp.write((uint8_t)192); // answer name is a pointer
|
|||
_udp.write((uint8_t)12); // pointer to offset at 0x00c
|
|||
|
|||
_udp.write((uint8_t)0); // 0x0001 answer is type A query (host address)
|
|||
_udp.write((uint8_t)1); |
|||
|
|||
_udp.write((uint8_t)0); //0x0001 answer is class IN (internet address)
|
|||
_udp.write((uint8_t)1); |
|||
|
|||
_udp.write((unsigned char*)&_ttl, 4); |
|||
|
|||
// Length of RData is 4 bytes (because, in this case, RData is IPv4)
|
|||
_udp.write((uint8_t)0); |
|||
_udp.write((uint8_t)4); |
|||
_udp.write(_resolvedIP, sizeof(_resolvedIP)); |
|||
_udp.endPacket(); |
|||
|
|||
|
|||
|
|||
#ifdef DEBUG |
|||
DEBUG_OUTPUT.print("DNS responds: "); |
|||
DEBUG_OUTPUT.print(_resolvedIP[0]); |
|||
DEBUG_OUTPUT.print("."); |
|||
DEBUG_OUTPUT.print(_resolvedIP[1]); |
|||
DEBUG_OUTPUT.print("."); |
|||
DEBUG_OUTPUT.print(_resolvedIP[2]); |
|||
DEBUG_OUTPUT.print("."); |
|||
DEBUG_OUTPUT.print(_resolvedIP[3]); |
|||
DEBUG_OUTPUT.print(" for "); |
|||
DEBUG_OUTPUT.println(getDomainNameWithoutWwwPrefix()); |
|||
#endif |
|||
} |
|||
|
|||
void DNSServer::replyWithCustomCode() |
|||
{ |
|||
_dnsHeader->QR = DNS_QR_RESPONSE; |
|||
_dnsHeader->RCode = (unsigned char)_errorReplyCode; |
|||
_dnsHeader->QDCount = 0; |
|||
|
|||
_udp.beginPacket(_udp.remoteIP(), _udp.remotePort()); |
|||
_udp.write(_buffer, sizeof(DNSHeader)); |
|||
_udp.endPacket(); |
|||
} |
@ -0,0 +1,72 @@ |
|||
#ifndef DNSServer_h |
|||
#define DNSServer_h |
|||
#include <WiFiUdp.h> |
|||
|
|||
#define DNS_QR_QUERY 0 |
|||
#define DNS_QR_RESPONSE 1 |
|||
#define DNS_OPCODE_QUERY 0 |
|||
|
|||
enum class DNSReplyCode |
|||
{ |
|||
NoError = 0, |
|||
FormError = 1, |
|||
ServerFailure = 2, |
|||
NonExistentDomain = 3, |
|||
NotImplemented = 4, |
|||
Refused = 5, |
|||
YXDomain = 6, |
|||
YXRRSet = 7, |
|||
NXRRSet = 8 |
|||
}; |
|||
|
|||
struct DNSHeader |
|||
{ |
|||
uint16_t ID; // identification number
|
|||
unsigned char RD : 1; // recursion desired
|
|||
unsigned char TC : 1; // truncated message
|
|||
unsigned char AA : 1; // authoritive answer
|
|||
unsigned char OPCode : 4; // message_type
|
|||
unsigned char QR : 1; // query/response flag
|
|||
unsigned char RCode : 4; // response code
|
|||
unsigned char Z : 3; // its z! reserved
|
|||
unsigned char RA : 1; // recursion available
|
|||
uint16_t QDCount; // number of question entries
|
|||
uint16_t ANCount; // number of answer entries
|
|||
uint16_t NSCount; // number of authority entries
|
|||
uint16_t ARCount; // number of resource entries
|
|||
}; |
|||
|
|||
class DNSServer |
|||
{ |
|||
public: |
|||
DNSServer(); |
|||
void processNextRequest(); |
|||
void setErrorReplyCode(const DNSReplyCode &replyCode); |
|||
void setTTL(const uint32_t &ttl); |
|||
|
|||
// Returns true if successful, false if there are no sockets available
|
|||
bool start(const uint16_t &port, |
|||
const String &domainName, |
|||
const IPAddress &resolvedIP); |
|||
// stops the DNS server
|
|||
void stop(); |
|||
|
|||
private: |
|||
WiFiUDP _udp; |
|||
uint16_t _port; |
|||
String _domainName; |
|||
unsigned char _resolvedIP[4]; |
|||
int _currentPacketSize; |
|||
unsigned char* _buffer; |
|||
DNSHeader* _dnsHeader; |
|||
uint32_t _ttl; |
|||
DNSReplyCode _errorReplyCode; |
|||
|
|||
void downcaseAndRemoveWwwPrefix(String &domainName); |
|||
String getDomainNameWithoutWwwPrefix(); |
|||
bool requestIncludesOnlyOneQuestion(); |
|||
void replyWithIP(); |
|||
void replyWithCustomCode(); |
|||
}; |
|||
#endif |
|||
|
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 331 KiB |
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 357 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 51 KiB |
@ -0,0 +1,399 @@ |
|||
<!doctype html> |
|||
<html lang="en-US"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<style> |
|||
h1 { |
|||
font-size: 3em; |
|||
} |
|||
img { |
|||
max-width: 100%; |
|||
/*position: absolute;*/ |
|||
} |
|||
.rel { |
|||
position: relative; |
|||
} |
|||
#capture { |
|||
height: 100vh; |
|||
} |
|||
.text { |
|||
position: absolute; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
flex-wrap: wrap; |
|||
z-index: -1; |
|||
} |
|||
.text > div { |
|||
/*font-size: 20px;*/ |
|||
font-family: monospace; |
|||
color: #695858; |
|||
letter-spacing: .1em; |
|||
padding: 13px; |
|||
transform: rotate(355deg); |
|||
} |
|||
#button-wrapper { |
|||
position: fixed; |
|||
top: 300px; |
|||
left: 0; |
|||
width: 250px; |
|||
animation: translate 20s infinite linear; |
|||
z-index: 9999; |
|||
} |
|||
#imgButton { |
|||
background: linear-gradient(to right, #CB3066, #16BFFD); |
|||
background-size: 300% 300%; |
|||
color: #fff; |
|||
font-size: 18px; |
|||
line-height: 30px; |
|||
padding: 10px 30px; |
|||
text-decoration: none; |
|||
display: inline-block; |
|||
text-transform: uppercase; |
|||
position: absolute; |
|||
left: -300px; |
|||
border: 0; |
|||
outline: 0; |
|||
box-shadow: 0 5px 10px 0 rgba(205, 209, 215, 0.8); |
|||
animation: gradient 2.5s ease infinite, inmotion 1.5s alternate infinite ease-in-out; |
|||
} |
|||
@keyframes translate { |
|||
to { transform: translateX(200%); } |
|||
} |
|||
@keyframes inmotion { |
|||
to { transform: translateY(100px); } |
|||
} |
|||
@keyframes gradient { |
|||
0% { background-position: 0% 50% } |
|||
50% { background-position: 100% 50% } |
|||
100% { background-position: 0% 50% } |
|||
} |
|||
</style> |
|||
</div> |
|||
<body id="capture"> |
|||
<div id="button-wrapper"> |
|||
<button id="imgButton">CLICK TO SAVE YOUR KATAMARI</button> |
|||
</div> |
|||
<div class="text"> |
|||
<div>Bouchéstraße</div> |
|||
<div>Alt-Treptow</div> |
|||
<div>Berlin</div> |
|||
<div>Kiefholzstraße</div> |
|||
<div>Elsenstraße</div> |
|||
<div>Harzer Straße</div> |
|||
<div>Wildenbruchstraße</div> |
|||
<div>Heidelberger Straße</div> |
|||
<div>Karl-Kunger-Straße</div> |
|||
<div>Sülzhayner Straße</div> |
|||
<div>Schmollerstraße</div> |
|||
<div>Mengerzeile</div> |
|||
<div>Am Treptower Park</div> |
|||
<div>Puschkinallee</div> |
|||
<div>Martin-Hoffman-Straße</div> |
|||
<div>Eichenstraße</div> |
|||
<div>Fanny-Zobel-Straße</div> |
|||
<div>Matthesstraße</div> |
|||
<div>Beermannstraße</div> |
|||
<div>Jordanstraße</div> |
|||
<div>Lohmühlenstraße</div> |
|||
<div>Am Flutgraben</div> |
|||
<div>Schleusenufer</div> |
|||
<div>Landwehr Canal</div> |
|||
<div>Spree</div> |
|||
<div>Treptower Park</div> |
|||
<div>Rummelsburger See</div> |
|||
<div>Schlesisches Tor</div> |
|||
<div>Skalitzer Straße</div> |
|||
<div>Köpenicker Straße</div> |
|||
<div>Brückenstraße</div> |
|||
<div>Rungestraße</div> |
|||
<div>Ohmstraße</div> |
|||
<div>Michaelkirchstraße</div> |
|||
<div>Michaelkirchplatz</div> |
|||
<div>Heinrich-Heine-Straße</div> |
|||
<div>Jannowitzbrücke</div> |
|||
<div>Schlesisches Straße</div> |
|||
<div>Cuvrystraße</div> |
|||
<div>Wrangelstraße</div> |
|||
<div>Taborstraße</div> |
|||
<div>Heckmannufer</div> |
|||
<div>Görlitzer Ufer</div> |
|||
<div>Görlitzer Park</div> |
|||
<div>Falckenstein Straße</div> |
|||
<div>Oppelner Straße</div> |
|||
<div>Sorauer Straße</div> |
|||
<div>Lübbener Straße</div> |
|||
<div>Plesser Straße</div> |
|||
<div>Krüllsstraße</div> |
|||
<div>Kiehlufer</div> |
|||
<div>Treptower Straße</div> |
|||
<div>Hüttenroder Weg</div> |
|||
<div>Onckenstraße</div> |
|||
<div>Lexisstraße</div> |
|||
<div>Grabowstraße</div> |
|||
<div>Wiener Straße</div> |
|||
<div>Reichenberger Straße</div> |
|||
<div>Paul-Lincke-Ufer</div> |
|||
<div>Glogauer Straße</div> |
|||
<div>Liegnitzer Straße</div> |
|||
<div>Forster Straße</div> |
|||
<div>Ohlauer Straße</div> |
|||
<div>Lausitzer Straße</div> |
|||
<div>Manteuffelstraße</div> |
|||
<div>Mariannenstraße</div> |
|||
<div>Muskauer Straße</div> |
|||
<div>Warschauer Straße</div> |
|||
<div>Revaler Straße</div> |
|||
<div>Simon-Dach-Straße</div> |
|||
<div>Boxhagener Platz</div> |
|||
<div>Kreuzberg</div> |
|||
<div>Neukölln</div> |
|||
<div>Bouchéstraße</div> |
|||
<div>Alt-Treptow</div> |
|||
<div>Berlin</div> |
|||
<div>Kiefholzstraße</div> |
|||
<div>Elsenstraße</div> |
|||
<div>Harzer Straße</div> |
|||
<div>Wildenbruchstraße</div> |
|||
<div>Heidelberger Straße</div> |
|||
<div>Karl-Kunger-Straße</div> |
|||
<div>Sülzhayner Straße</div> |
|||
<div>Schmollerstraße</div> |
|||
<div>Mengerzeile</div> |
|||
<div>Am Treptower Park</div> |
|||
<div>Puschkinallee</div> |
|||
<div>Martin-Hoffman-Straße</div> |
|||
<div>Eichenstraße</div> |
|||
<div>Fanny-Zobel-Straße</div> |
|||
<div>Matthesstraße</div> |
|||
<div>Beermannstraße</div> |
|||
<div>Jordanstraße</div> |
|||
<div>Lohmühlenstraße</div> |
|||
<div>Am Flutgraben</div> |
|||
<div>Schleusenufer</div> |
|||
<div>Landwehr Canal</div> |
|||
<div>Spree</div> |
|||
<div>Treptower Park</div> |
|||
<div>Rummelsburger See</div> |
|||
<div>Schlesisches Tor</div> |
|||
<div>Skalitzer Straße</div> |
|||
<div>Köpenicker Straße</div> |
|||
<div>Brückenstraße</div> |
|||
<div>Rungestraße</div> |
|||
<div>Ohmstraße</div> |
|||
<div>Michaelkirchstraße</div> |
|||
<div>Michaelkirchplatz</div> |
|||
<div>Heinrich-Heine-Straße</div> |
|||
<div>Jannowitzbrücke</div> |
|||
<div>Schlesisches Straße</div> |
|||
<div>Cuvrystraße</div> |
|||
<div>Wrangelstraße</div> |
|||
<div>Taborstraße</div> |
|||
<div>Heckmannufer</div> |
|||
<div>Görlitzer Ufer</div> |
|||
<div>Görlitzer Park</div> |
|||
<div>Falckenstein Straße</div> |
|||
<div>Oppelner Straße</div> |
|||
<div>Sorauer Straße</div> |
|||
<div>Lübbener Straße</div> |
|||
<div>Plesser Straße</div> |
|||
<div>Krüllsstraße</div> |
|||
<div>Kiehlufer</div> |
|||
<div>Treptower Straße</div> |
|||
<div>Hüttenroder Weg</div> |
|||
<div>Onckenstraße</div> |
|||
<div>Lexisstraße</div> |
|||
<div>Grabowstraße</div> |
|||
<div>Wiener Straße</div> |
|||
<div>Reichenberger Straße</div> |
|||
<div>Paul-Lincke-Ufer</div> |
|||
<div>Glogauer Straße</div> |
|||
<div>Liegnitzer Straße</div> |
|||
<div>Forster Straße</div> |
|||
<div>Ohlauer Straße</div> |
|||
<div>Lausitzer Straße</div> |
|||
<div>Manteuffelstraße</div> |
|||
<div>Mariannenstraße</div> |
|||
<div>Muskauer Straße</div> |
|||
<div>Warschauer Straße</div> |
|||
<div>Revaler Straße</div> |
|||
<div>Simon-Dach-Straße</div> |
|||
<div>Boxhagener Platz</div> |
|||
<div>Kreuzberg</div> |
|||
<div>Neukölln</div> |
|||
<div>Bouchéstraße</div> |
|||
<div>Alt-Treptow</div> |
|||
<div>Berlin</div> |
|||
<div>Kiefholzstraße</div> |
|||
<div>Elsenstraße</div> |
|||
<div>Harzer Straße</div> |
|||
<div>Wildenbruchstraße</div> |
|||
<div>Heidelberger Straße</div> |
|||
<div>Karl-Kunger-Straße</div> |
|||
<div>Sülzhayner Straße</div> |
|||
<div>Schmollerstraße</div> |
|||
<div>Mengerzeile</div> |
|||
<div>Am Treptower Park</div> |
|||
<div>Puschkinallee</div> |
|||
<div>Martin-Hoffman-Straße</div> |
|||
<div>Eichenstraße</div> |
|||
<div>Fanny-Zobel-Straße</div> |
|||
<div>Matthesstraße</div> |
|||
<div>Beermannstraße</div> |
|||
<div>Jordanstraße</div> |
|||
<div>Lohmühlenstraße</div> |
|||
<div>Am Flutgraben</div> |
|||
<div>Schleusenufer</div> |
|||
<div>Landwehr Canal</div> |
|||
<div>Spree</div> |
|||
<div>Treptower Park</div> |
|||
<div>Rummelsburger See</div> |
|||
<div>Schlesisches Tor</div> |
|||
<div>Skalitzer Straße</div> |
|||
<div>Köpenicker Straße</div> |
|||
<div>Brückenstraße</div> |
|||
<div>Rungestraße</div> |
|||
<div>Ohmstraße</div> |
|||
<div>Michaelkirchstraße</div> |
|||
<div>Michaelkirchplatz</div> |
|||
<div>Heinrich-Heine-Straße</div> |
|||
<div>Jannowitzbrücke</div> |
|||
<div>Schlesisches Straße</div> |
|||
<div>Cuvrystraße</div> |
|||
<div>Wrangelstraße</div> |
|||
<div>Taborstraße</div> |
|||
<div>Heckmannufer</div> |
|||
<div>Görlitzer Ufer</div> |
|||
<div>Görlitzer Park</div> |
|||
<div>Falckenstein Straße</div> |
|||
<div>Oppelner Straße</div> |
|||
<div>Sorauer Straße</div> |
|||
<div>Lübbener Straße</div> |
|||
<div>Plesser Straße</div> |
|||
<div>Krüllsstraße</div> |
|||
<div>Kiehlufer</div> |
|||
<div>Treptower Straße</div> |
|||
<div>Hüttenroder Weg</div> |
|||
<div>Onckenstraße</div> |
|||
<div>Lexisstraße</div> |
|||
<div>Grabowstraße</div> |
|||
<div>Wiener Straße</div> |
|||
<div>Reichenberger Straße</div> |
|||
<div>Paul-Lincke-Ufer</div> |
|||
<div>Glogauer Straße</div> |
|||
<div>Liegnitzer Straße</div> |
|||
<div>Forster Straße</div> |
|||
<div>Ohlauer Straße</div> |
|||
<div>Lausitzer Straße</div> |
|||
<div>Manteuffelstraße</div> |
|||
<div>Mariannenstraße</div> |
|||
<div>Muskauer Straße</div> |
|||
<div>Warschauer Straße</div> |
|||
<div>Revaler Straße</div> |
|||
<div>Simon-Dach-Straße</div> |
|||
<div>Boxhagener Platz</div> |
|||
<div>Kreuzberg</div> |
|||
<div>Neukölln</div> |
|||
<div>Bouchéstraße</div> |
|||
<div>Alt-Treptow</div> |
|||
<div>Berlin</div> |
|||
<div>Kiefholzstraße</div> |
|||
<div>Elsenstraße</div> |
|||
<div>Harzer Straße</div> |
|||
<div>Wildenbruchstraße</div> |
|||
<div>Heidelberger Straße</div> |
|||
<div>Karl-Kunger-Straße</div> |
|||
<div>Sülzhayner Straße</div> |
|||
<div>Schmollerstraße</div> |
|||
<div>Mengerzeile</div> |
|||
<div>Am Treptower Park</div> |
|||
<div>Puschkinallee</div> |
|||
<div>Martin-Hoffman-Straße</div> |
|||
<div>Eichenstraße</div> |
|||
<div>Fanny-Zobel-Straße</div> |
|||
<div>Matthesstraße</div> |
|||
<div>Beermannstraße</div> |
|||
<div>Jordanstraße</div> |
|||
<div>Lohmühlenstraße</div> |
|||
<div>Am Flutgraben</div> |
|||
<div>Schleusenufer</div> |
|||
<div>Landwehr Canal</div> |
|||
<div>Spree</div> |
|||
<div>Treptower Park</div> |
|||
<div>Rummelsburger See</div> |
|||
<div>Schlesisches Tor</div> |
|||
<div>Skalitzer Straße</div> |
|||
<div>Köpenicker Straße</div> |
|||
<div>Brückenstraße</div> |
|||
<div>Rungestraße</div> |
|||
<div>Ohmstraße</div> |
|||
<div>Michaelkirchstraße</div> |
|||
<div>Michaelkirchplatz</div> |
|||
<div>Heinrich-Heine-Straße</div> |
|||
<div>Jannowitzbrücke</div> |
|||
<div>Schlesisches Straße</div> |
|||
<div>Cuvrystraße</div> |
|||
<div>Wrangelstraße</div> |
|||
<div>Taborstraße</div> |
|||
<div>Heckmannufer</div> |
|||
<div>Görlitzer Ufer</div> |
|||
<div>Görlitzer Park</div> |
|||
<div>Falckenstein Straße</div> |
|||
<div>Oppelner Straße</div> |
|||
<div>Sorauer Straße</div> |
|||
<div>Lübbener Straße</div> |
|||
<div>Plesser Straße</div> |
|||
<div>Krüllsstraße</div> |
|||
<div>Kiehlufer</div> |
|||
<div>Treptower Straße</div> |
|||
<div>Hüttenroder Weg</div> |
|||
<div>Onckenstraße</div> |
|||
<div>Lexisstraße</div> |
|||
<div>Grabowstraße</div> |
|||
<div>Wiener Straße</div> |
|||
<div>Reichenberger Straße</div> |
|||
<div>Paul-Lincke-Ufer</div> |
|||
<div>Glogauer Straße</div> |
|||
<div>Liegnitzer Straße</div> |
|||
<div>Forster Straße</div> |
|||
<div>Ohlauer Straße</div> |
|||
<div>Lausitzer Straße</div> |
|||
<div>Manteuffelstraße</div> |
|||
<div>Mariannenstraße</div> |
|||
<div>Muskauer Straße</div> |
|||
<div>Warschauer Straße</div> |
|||
<div>Revaler Straße</div> |
|||
<div>Simon-Dach-Straße</div> |
|||
<div>Boxhagener Platz</div> |
|||
<div>Kreuzberg</div> |
|||
<div>Neukölln</div> |
|||
</div> |
|||
|
|||
<div> |
|||
<marquee><i><h1>PLAGUE RAVE 2020 <progress></progress></h1></i></marquee> |
|||
|
|||
<div class="rel"> |
|||
<img src="img/mask.gif" /> |
|||
<br/> |
|||
<img src="img/berghain.gif" /> |
|||
</br> |
|||
<img src="img/TIER-scooter-final-1024x1024.png" /> |
|||
</div> |
|||
<marquee><i><h1><progress></progress> PLAGUE RAVE 2020</h1></i></marquee> |
|||
|
|||
<div class="rel"> |
|||
<img src="img/source.gif" /> |
|||
<img class="left" src="img/thumbnail.gif" /> |
|||
<img src="img/Berlin_TV_Tower.png" /> |
|||
</div> |
|||
|
|||
<marquee><i><h1><progress></progress> PLAGUE RAVE 2020</h1></i></marquee> |
|||
</div> |
|||
|
|||
<script type='text/javascript' src='js/jquery-3.5.1.slim.min.js'></script> |
|||
<script type='text/javascript' src='js/howler.min.js'></script> |
|||
<script type='text/javascript' src='js/html2canvas.min.js'></script> |
|||
<script type='text/javascript' src='js/kh.js'></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,801 @@ |
|||
/* PLAGUE RAVE 2020 by chootka for e-ridin`dirty / OPENCOIL 2020 * Dennis de Bel * Anton ??? |
|||
|
|||
Adapted from kathack.com, Alex Leone, David Nufer, David Truong, © 2011-03-11 */ |
|||
|
|||
var CSS_TRANSFORM = null, |
|||
CSS_TRANSFORM_ORIGIN = null, |
|||
POSSIBLE_TRANSFORM_PREFIXES = ['-webkit-', '-moz-', '-o-', '-ms-', ''], |
|||
khFirst = false, |
|||
vibrateInterval = null; |
|||
|
|||
/* When running twice on one page, update pick-uppable nodes instead of |
|||
* creating more. |
|||
*/ |
|||
if (!window.khNodes) { |
|||
khFirst = true; |
|||
window.khNodes = new StickyNodes(); |
|||
} |
|||
|
|||
function getCssTransform() { |
|||
var i, d = document.createElement('div'), pre; |
|||
for (i = 0; i < POSSIBLE_TRANSFORM_PREFIXES.length; i++) { |
|||
pre = POSSIBLE_TRANSFORM_PREFIXES[i]; |
|||
d.style.setProperty(pre + 'transform', 'rotate(1rad) scaleX(2)', null); |
|||
if (d.style.getPropertyValue(pre + 'transform')) { |
|||
CSS_TRANSFORM = pre + 'transform'; |
|||
CSS_TRANSFORM_ORIGIN = pre + 'transform-origin'; |
|||
return; |
|||
} |
|||
} |
|||
alert("Your browser doesn't support CSS tranforms!"); |
|||
throw "Your browser doesn't support CSS tranforms!"; |
|||
} |
|||
getCssTransform(); |
|||
|
|||
/** |
|||
* Vibration support |
|||
*/ |
|||
|
|||
// Starts vibration at passed in level
|
|||
function startVibrate(duration) { |
|||
navigator.vibrate(duration); |
|||
} |
|||
|
|||
// Stops vibration
|
|||
function stopVibrate() { |
|||
// Clear interval and stop persistent vibrating
|
|||
if(vibrateInterval) clearInterval(vibrateInterval); |
|||
navigator.vibrate(0); |
|||
} |
|||
|
|||
// Start persistent vibration at given duration and interval
|
|||
// Assumes a number value is given
|
|||
function startPeristentVibrate(duration, interval) { |
|||
vibrateInterval = setInterval(function() { |
|||
startVibrate(duration); |
|||
}, interval); |
|||
} |
|||
|
|||
/** |
|||
* Returns true if the circle intersects the element rectangle. |
|||
* 0 | 1 | 2 |
|||
* ------------------ |
|||
* 3 | 4 | 5 |
|||
* ------------------ |
|||
* 6 | 7 | 9 |
|||
*/ |
|||
function circleGridObjInt(cx, cy, cr, cr2, go) { |
|||
var dx, dy; |
|||
if (cx < go.left) { |
|||
dx = go.left - cx; |
|||
if (cy < go.top) { /* zone 0. */ |
|||
dy = go.top - cy; |
|||
return ((dx * dx + dy * dy) <= cr2); |
|||
} else if (cy <= go.bottom) { /* zone 3. */ |
|||
return (dx <= cr); |
|||
} else { /* zone 6. */ |
|||
dy = cy - go.bottom; |
|||
return ((dx * dx + dy * dy) <= cr2); |
|||
} |
|||
} else if (cx <= go.right) { |
|||
if (cy < go.top) { /* zone 1. */ |
|||
return ((go.top - cy) <= cr); |
|||
} else if (cy <= go.bottom) { /* zone 4. */ |
|||
return true; |
|||
} else { /* zone 7. */ |
|||
return ((cy - go.bottom) <= cr); |
|||
} |
|||
} else { |
|||
dx = cx - go.right; |
|||
if (cy < go.top) { /* zone 2. */ |
|||
dy = go.top - cy; |
|||
return ((dx * dx + dy * dy) <= cr2); |
|||
} else if (cy <= go.bottom) { /* zone 5. */ |
|||
return (dx <= cr); |
|||
} else { /* zone 9. */ |
|||
dy = cy - go.bottom; |
|||
return ((dx * dx + dy * dy) <= cr2); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns [x,y] where the rectangle is closest to (cx, cy). |
|||
* 0 | 1 | 2 |
|||
* ------------------ |
|||
* 3 | 4 | 5 |
|||
* ------------------ |
|||
* 6 | 7 | 9 |
|||
*/ |
|||
function getClosestPoint(cx, cy, go) { |
|||
var dx, dy; |
|||
if (cx < go.left) { |
|||
dx = go.left - cx; |
|||
if (cy < go.top) { /* zone 0. */ |
|||
return [go.left, go.top]; |
|||
} else if (cy <= go.bottom) { /* zone 3. */ |
|||
return [go.left, cy]; |
|||
} else { /* zone 6. */ |
|||
return [go.left, go.bottom]; |
|||
} |
|||
} else if (cx <= go.right) { |
|||
if (cy < go.top) { /* zone 1. */ |
|||
return [cx, go.top]; |
|||
} else if (cy <= go.bottom) { /* zone 4. */ |
|||
return [cx, cy]; |
|||
} else { /* zone 7. */ |
|||
return [cx, go.bottom]; |
|||
} |
|||
} else { |
|||
dx = cx - go.right; |
|||
if (cy < go.top) { /* zone 2. */ |
|||
return [go.right, go.top]; |
|||
} else if (cy <= go.bottom) { /* zone 5. */ |
|||
return [go.right, cy]; |
|||
} else { /* zone 9. */ |
|||
return [go.right, go.bottom]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns the "volume" of the grid object. |
|||
*/ |
|||
function gridObjVol(go) { |
|||
return go.w * go.h * Math.min(go.w, go.h); |
|||
} |
|||
|
|||
function StickyNodes() { |
|||
var domNodes = [], |
|||
grid = [], |
|||
GRIDX = 100, |
|||
GRIDY = 100, |
|||
REPLACE_WORDS_IN = { |
|||
a: 1, b: 1, big: 1, body: 1, cite:1, code: 1, dd: 1, div: 1, |
|||
dt: 1, em: 1, font: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1, |
|||
i: 1, label: 1, legend: 1, li: 1, p: 1, pre: 1, small: 1, |
|||
span: 1, strong: 1, sub: 1, sup: 1, td: 1, th: 1, tt: 1 |
|||
}; |
|||
|
|||
function addDomNode(el) { |
|||
if (el !== undefined && el !== null) { |
|||
el.khIgnore = true; |
|||
domNodes.push(el); |
|||
} |
|||
} |
|||
this.addDomNode = addDomNode; |
|||
|
|||
this.addWords = function (el) { |
|||
var textEls = []; |
|||
|
|||
function shouldAddChildren(el) { |
|||
return el.tagName && REPLACE_WORDS_IN[el.tagName.toLowerCase()]; |
|||
} |
|||
|
|||
function buildTextEls(el, shouldAdd) { |
|||
var i, len; |
|||
if (shouldAdd && el.nodeType === Node.TEXT_NODE && |
|||
el.nodeValue.trim().length > 0) { |
|||
textEls.push(el); |
|||
return; |
|||
} |
|||
if (!el.childNodes || el.khIgnore) { |
|||
return; |
|||
} |
|||
shouldAdd = shouldAddChildren(el); |
|||
for (i = 0, len = el.childNodes.length; i < len; i++) { |
|||
buildTextEls(el.childNodes[i], shouldAdd); |
|||
} |
|||
} |
|||
|
|||
function wordsToSpans(textEl) { |
|||
var p = textEl.parentNode, |
|||
words = textEl.nodeValue.split(/\s+/), |
|||
ws = textEl.nodeValue.split(/\S+/), |
|||
i, n, len = Math.max(words.length, ws.length); |
|||
/* preserve whitespace for pre tags. */ |
|||
if (ws.length > 0 && ws[0].length === 0) { |
|||
ws.shift(); |
|||
} |
|||
for (i = 0; i < len; i++) { |
|||
if (i < words.length && words[i].length > 0) { |
|||
n = document.createElement('span'); |
|||
n.innerHTML = words[i]; |
|||
p.insertBefore(n, textEl); |
|||
addDomNode(n); |
|||
} |
|||
if (i < ws.length && ws[i].length > 0) { |
|||
n = document.createTextNode(ws[i]); |
|||
p.insertBefore(n, textEl); |
|||
} |
|||
} |
|||
p.removeChild(textEl); |
|||
} |
|||
|
|||
buildTextEls(el, shouldAddChildren(el)); |
|||
textEls.map(wordsToSpans); |
|||
}; |
|||
|
|||
/* includes el. */ |
|||
this.addTagNames = function (el, tagNames) { |
|||
var tname = el.tagName && el.tagName.toLowerCase(), |
|||
i, j, els, len; |
|||
if (el.khIgnore) { |
|||
return; |
|||
} |
|||
if (tagNames.indexOf(tname) !== -1) { |
|||
addDomNode(el); |
|||
} |
|||
if (!el.getElementsByTagName) { |
|||
return; |
|||
} |
|||
for (i = 0; i < tagNames.length; i++) { |
|||
els = el.getElementsByTagName(tagNames[i]); |
|||
for (j = 0, len = els.length; j < len; j++) { |
|||
if (!els[j].khIgnore) { |
|||
addDomNode(els[j]); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
this.finalize = function (docW, docH) { |
|||
var xi, yi, i, len, startXI, startYI, el, go, off, w, h, |
|||
endXI = Math.floor(docW / GRIDX) + 1, |
|||
endYI = Math.floor(docH / GRIDY) + 1; |
|||
|
|||
/* initialize grid. */ |
|||
grid = new Array(endXI); |
|||
for (xi = 0; xi < endXI; xi++) { |
|||
grid[xi] = new Array(endYI); |
|||
} |
|||
|
|||
/* add nodes into grid. */ |
|||
for (i = 0, len = domNodes.length; i < len; i++) { |
|||
el = domNodes[i]; |
|||
if (el.khPicked) { |
|||
continue; |
|||
} |
|||
off = jQuery(el).offset(); |
|||
w = jQuery(el).width(); |
|||
h = jQuery(el).height(); |
|||
go = { |
|||
el: domNodes[i], /* dom element. */ |
|||
left: off.left, |
|||
right: off.left + w, |
|||
top: off.top, |
|||
bottom: off.top + h, |
|||
w: w, |
|||
h: h, |
|||
x: off.left + (w / 2), /* center x. */ |
|||
y: off.top + (h / 2), /* center y. */ |
|||
diag: Math.sqrt(((w * w) + (h * h)) / 4), /* center to corner */ |
|||
|
|||
/* these are for removing ourselves from the grid. */ |
|||
arrs: [], /* which arrays we're in (grid[x][y]). */ |
|||
idxs: [] /* what indexes. */ |
|||
}; |
|||
startXI = Math.floor(go.left / GRIDX); |
|||
startYI = Math.floor(go.top / GRIDY); |
|||
endXI = Math.floor((go.left + go.w) / GRIDX) + 1; |
|||
endYI = Math.floor((go.top + go.h) / GRIDY) + 1; |
|||
for (xi = startXI; xi < endXI; xi++) { |
|||
for (yi = startYI; yi < endYI; yi++) { |
|||
if (grid[xi] === undefined) { |
|||
grid[xi] = []; |
|||
} |
|||
if (grid[xi][yi] === undefined) { |
|||
grid[xi][yi] = [go]; |
|||
} else { |
|||
grid[xi][yi].push(go); |
|||
} |
|||
go.arrs.push(grid[xi][yi]); |
|||
go.idxs.push(grid[xi][yi].length - 1); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
function removeGridObj(go) { |
|||
var i; |
|||
for (i = 0; i < go.arrs.length; i++) { |
|||
go.arrs[i][go.idxs[i]] = undefined; |
|||
} |
|||
go.el.style.visibility = "hidden"; |
|||
go.el.khPicked = true; |
|||
delete go.arrs; |
|||
delete go.idxs; |
|||
} |
|||
|
|||
/** |
|||
* cb(gridObj) -> boolean true if the object should be removed. |
|||
*/ |
|||
this.removeIntersecting = function (x, y, r, cb) { |
|||
var xi, yi, arr, i, r2 = r * r, go, |
|||
startXI = Math.floor((x - r) / GRIDX), |
|||
startYI = Math.floor((y - r) / GRIDY), |
|||
endXI = Math.floor((x + r) / GRIDX) + 1, |
|||
endYI = Math.floor((y + r) / GRIDY) + 1; |
|||
for (xi = startXI; xi < endXI; xi++) { |
|||
if (grid[xi] === undefined) { |
|||
continue; |
|||
} |
|||
for (yi = startYI; yi < endYI; yi++) { |
|||
arr = grid[xi][yi]; |
|||
if (arr === undefined) { |
|||
continue; |
|||
} |
|||
for (i = 0; i < arr.length; i++) { |
|||
go = arr[i]; |
|||
if (go !== undefined && |
|||
circleGridObjInt(x, y, r, r2, go) && |
|||
cb(go)) { |
|||
removeGridObj(go); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
function PlayerBall(parentNode, stickyNodes, ballOpts) { |
|||
var x = 300, y = 300, |
|||
vx = 0, vy = 0, |
|||
radius = 20, |
|||
lastR = 0, /**< optimization: only resize when necessary. */ |
|||
docW = 10000, docH = 10000, |
|||
|
|||
attached = [], |
|||
attachedDiv, /* div to put attached nodes into. */ |
|||
canvas_el, |
|||
canvas_ctx, |
|||
color = ballOpts.color, |
|||
|
|||
accelTargetX = 0, accelTargetY = 0, |
|||
accel = false, |
|||
|
|||
VOL_MULT = ballOpts.VOL_MULT, |
|||
MAX_ATTACHED_VISIBLE = ballOpts.MAX_ATTACHED_VISIBLE, |
|||
CHECK_VOLS = ballOpts.CHECK_VOLS, |
|||
|
|||
/** |
|||
* which direction the ball is facing in the xy axis, in radians. |
|||
* th: 0 is facing dead East |
|||
* th: 1/2 PI is facing dead South |
|||
* note that this is like regular th on a graph with y inverted. |
|||
* Same rotation as css transform. |
|||
*/ |
|||
th = 0, |
|||
|
|||
/** |
|||
* Ball angle in the rotation axis / z plane, in radians. |
|||
* phi: 0 is pointing in the direction the ball is rolling. |
|||
* phi: 1/2 PI is pointing straight up (out of the page). |
|||
* note that forward rotation means phi -= 0.1. |
|||
*/ |
|||
phi = 0; |
|||
|
|||
this.init = function () { |
|||
canvas_el = document.createElement('canvas'); |
|||
canvas_el.width = radius * 2; |
|||
canvas_el.height = radius * 2; |
|||
canvas_el.style.cssText = 'position: absolute; z-index: 500;'; |
|||
parentNode.appendChild(canvas_el); |
|||
canvas_ctx = canvas_el.getContext('2d'); |
|||
|
|||
attachedDiv = document.createElement('div'); |
|||
// attachedDiv.setAttribute("id", "capture")
|
|||
parentNode.appendChild(attachedDiv); |
|||
}; |
|||
|
|||
this.setRadius = function (r) { |
|||
radius = r; |
|||
}; |
|||
|
|||
this.getState = function () { |
|||
return { |
|||
x: x, |
|||
y: y, |
|||
vx: vx, |
|||
vy: vy, |
|||
radius: radius, |
|||
th: th, |
|||
phi: phi, |
|||
}; |
|||
}; |
|||
|
|||
this.setState = function (s) { |
|||
x = s.x; |
|||
y = s.y; |
|||
vx = s.vx; |
|||
vy = s.vy; |
|||
radius = s.radius; |
|||
th = s.th; |
|||
phi = s.phi; |
|||
}; |
|||
|
|||
this.setXY = function (sx, sy) { |
|||
x = sx; |
|||
y = sy; |
|||
}; |
|||
|
|||
this.setTh = function (sth) { |
|||
th = sth; |
|||
}; |
|||
|
|||
this.setPhi = function (sphi) { |
|||
phi = sphi; |
|||
}; |
|||
|
|||
this.setColor = function (c) { |
|||
color = c; |
|||
}; |
|||
|
|||
this.setDocSize = function (w, h) { |
|||
docW = w; |
|||
docH = h; |
|||
}; |
|||
|
|||
this.setAccel = function (bool) { |
|||
accel = bool; |
|||
}; |
|||
|
|||
this.setAccelTarget = function (tx, ty) { |
|||
accelTargetX = tx; |
|||
accelTargetY = ty; |
|||
}; |
|||
|
|||
function getVol() { |
|||
return (4 * Math.PI * radius * radius * radius / 3); |
|||
} |
|||
|
|||
function grow(go) { |
|||
var newVol = getVol() + gridObjVol(go) * VOL_MULT; |
|||
radius = Math.pow(newVol * 3 / (4 * Math.PI), 1 / 3); |
|||
} |
|||
|
|||
function attachGridObj(go) { |
|||
var attXY = getClosestPoint(x, y, go), |
|||
dx = attXY[0] - x, |
|||
dy = attXY[1] - y,w |
|||
r = Math.sqrt(dx * dx + dy * dy), |
|||
attTh = 0 - th, |
|||
offLeft = attXY[0] - go.left, |
|||
offTop = attXY[1] - go.top, |
|||
offTh = Math.atan2(dy, dx) - th, |
|||
attX = r * Math.cos(offTh), |
|||
attY = r * Math.sin(offTh), |
|||
el = go.el.cloneNode(true), |
|||
go_jel = jQuery(go.el), |
|||
newAtt = { |
|||
el: el, |
|||
attX: attX, |
|||
attY: attY, |
|||
attT: 'translate(' + Math.round(attX) + 'px,' + |
|||
Math.round(attY) + 'px) ' + |
|||
'rotate(' + attTh + 'rad)', |
|||
r: r, |
|||
offTh: offTh, |
|||
offPhi: 0 - phi, |
|||
diag: go.diag, |
|||
removeR: r + go.diag, |
|||
visible: false, |
|||
display: go_jel.css('display') |
|||
}; |
|||
attached.push(newAtt); |
|||
// grow(go);
|
|||
el.style.position = 'absolute'; |
|||
el.style.left = (-offLeft) + 'px'; |
|||
el.style.top = (-offTop) + 'px'; |
|||
el.style.setProperty(CSS_TRANSFORM_ORIGIN, |
|||
offLeft + 'px ' + offTop + 'px', null); |
|||
el.style.display = 'none'; |
|||
/* copy computed styles from old object. */ |
|||
el.style.color = go_jel.css('color'); |
|||
el.style.textDecoration = go_jel.css('text-decoration'); |
|||
el.style.fontSize = go_jel.css('font-size'); |
|||
el.style.fontWeight = go_jel.css('font-weight'); |
|||
el.khIgnore = true; |
|||
attachedDiv.appendChild(el); |
|||
} |
|||
|
|||
/** |
|||
* @return true if the object should be removed from stickyNodes. |
|||
*/ |
|||
function removeIntCb(go) { |
|||
if (CHECK_VOLS && gridObjVol(go) > getVol()) { |
|||
return false; |
|||
} |
|||
attachGridObj(go); |
|||
return true; |
|||
} |
|||
|
|||
this.updatePhysics = function () { |
|||
var oldX = x, oldY = y, dx, dy, |
|||
bounce = false, |
|||
accelTh; |
|||
if (accel) { |
|||
accelTh = Math.atan2(accelTargetY - y, accelTargetX - x); |
|||
vx += Math.cos(accelTh) * 0.5; |
|||
vy += Math.sin(accelTh) * 0.5; |
|||
} else { |
|||
vx *= 0.95; |
|||
vy *= 0.95; |
|||
} |
|||
x += vx; |
|||
y += vy; |
|||
/* bounce ball on edges of document. */ |
|||
if (x - radius < 0) { |
|||
bounce = true; |
|||
x = radius + 1; |
|||
vx = -vx; |
|||
} else if (x + radius > docW) { |
|||
bounce = true; |
|||
x = docW - radius - 1; |
|||
vx = -vx; |
|||
} |
|||
if (y - radius < 0) { |
|||
bounce = true; |
|||
y = radius + 1; |
|||
vy = -vy; |
|||
} else if (y + radius > docH) { |
|||
bounce = true; |
|||
y = docH - radius - 1; |
|||
vy = -vy; |
|||
} |
|||
if (vx !== 0 || vy !== 0) { |
|||
th = Math.atan2(vy, vx); |
|||
dx = x - oldX; |
|||
dy = y - oldY; |
|||
/* arclen = th * r, so th = arclen / r. */ |
|||
phi -= Math.sqrt(dx * dx + dy * dy) / radius; |
|||
} |
|||
stickyNodes.removeIntersecting(x, y, radius, removeIntCb); |
|||
this.draw(); |
|||
}; |
|||
|
|||
function drawBall() { |
|||
var sx1, sy1, sx2, sy2, dx, dy, i, pct1, pct2, z1, z2; |
|||
/* move/resize canvas element. */ |
|||
canvas_el.style.left = (x - radius) + 'px'; |
|||
canvas_el.style.top = (y - radius) + 'px'; |
|||
if (radius != lastR) { |
|||
canvas_el.width = 2 * radius + 1; |
|||
canvas_el.height = 2 * radius + 1; |
|||
lastR = radius; |
|||
} |
|||
/* draw white circle. */ |
|||
canvas_ctx.clearRect(0, 0, 2 * radius, 2 * radius); |
|||
canvas_ctx.fillStyle = "#ff0000"; |
|||
canvas_ctx.beginPath(); |
|||
canvas_ctx.arc(radius, radius, radius - 1, 0, Math.PI * 2, true); |
|||
canvas_ctx.fill(); |
|||
/* draw outer border. */ |
|||
canvas_ctx.strokeStyle = color; |
|||
canvas_ctx.beginPath(); |
|||
canvas_ctx.arc(radius, radius, radius - 1, 0, Math.PI * 2, true); |
|||
canvas_ctx.stroke(); |
|||
/* draw stripes. */ |
|||
canvas_ctx.fillStyle = color; |
|||
sx1 = radius + radius * Math.cos(th + Math.PI / 16); |
|||
sy1 = radius + radius * Math.sin(th + Math.PI / 16); |
|||
sx2 = radius + radius * Math.cos(th - Math.PI / 16); |
|||
sy2 = radius + radius * Math.sin(th - Math.PI / 16); |
|||
dx = (radius + radius * Math.cos(th + Math.PI * 15 / 16)) - sx1; |
|||
dy = (radius + radius * Math.sin(th + Math.PI * 15 / 16)) - sy1; |
|||
for (i = 0; i < Math.PI * 2; i += Math.PI / 7) { |
|||
pct1 = (-Math.cos(phi + i) + 1) / 2; |
|||
pct2 = (-Math.cos(phi + i + Math.PI / 32) + 1) / 2; |
|||
z1 = Math.sin(phi + i); |
|||
z2 = Math.sin(phi + i + Math.PI / 32); |
|||
if (z1 > 0 && z2 > 0) { |
|||
canvas_ctx.beginPath(); |
|||
canvas_ctx.moveTo(sx1 + pct1 * dx, sy1 + pct1 * dy); |
|||
canvas_ctx.lineTo(sx1 + pct2 * dx, sy1 + pct2 * dy); |
|||
canvas_ctx.lineTo(sx2 + pct2 * dx, sy2 + pct2 * dy); |
|||
canvas_ctx.lineTo(sx2 + pct1 * dx, sy2 + pct1 * dy); |
|||
canvas_ctx.fill(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @return true if the attached object is roughly visible. |
|||
*/ |
|||
function drawAttached(att) { |
|||
var oth = th + att.offTh, |
|||
ophi = phi + att.offPhi, |
|||
ox = att.r * Math.cos(oth), |
|||
oy = att.r * Math.sin(oth), |
|||
dx = (att.r * Math.cos((th - att.offTh) + Math.PI)) - ox, |
|||
dy = (att.r * Math.sin((th - att.offTh) + Math.PI)) - oy, |
|||
pct = (-Math.cos(ophi) + 1) / 2, |
|||
cx = ox + pct * dx, |
|||
cy = oy + pct * dy, |
|||
oz = att.r * Math.sin(ophi); |
|||
if (oz < 0 && Math.sqrt(cx * cx + cy * cy) + att.diag < radius) { |
|||
/* hidden behind circle. */ |
|||
if (att.visible) { |
|||
att.visible = false; |
|||
att.el.style.display = "none"; |
|||
} |
|||
return false; |
|||
} |
|||
/* attached node is visible. */ |
|||
if (!att.visible) { |
|||
att.visible = true; |
|||
att.el.style.display = att.display; |
|||
} |
|||
//att.el.style.zIndex = 500 + Math.round(oz);
|
|||
att.el.style.zIndex = (oz > 0)? 501 : 499; |
|||
att.el.style.setProperty( |
|||
CSS_TRANSFORM, |
|||
'translate(' + x + 'px,' + y + 'px) ' + |
|||
'rotate(' + th + 'rad) ' + |
|||
'scaleX(' + Math.cos(ophi) + ') ' + |
|||
att.attT, null); |
|||
return true; |
|||
} |
|||
|
|||
function onAttachedRemoved(att) { |
|||
attachedDiv.removeChild(att.el); |
|||
delete att.el; |
|||
} |
|||
|
|||
this.draw = function () { |
|||
var i, att, numAttachedVisible = 0; |
|||
drawBall(); |
|||
for (i = attached.length; --i >= 0;) { |
|||
att = attached[i]; |
|||
if (att.removeR < radius) { |
|||
attached.splice(i, 1).map(onAttachedRemoved); |
|||
} else if (drawAttached(att)) { |
|||
if (++numAttachedVisible > MAX_ATTACHED_VISIBLE) { |
|||
/* remove older items and stop. */ |
|||
attached.splice(0, i).map(onAttachedRemoved); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
function preventDefault(event) { |
|||
event.preventDefault(); |
|||
event.returnValue = false; |
|||
return false; |
|||
} |
|||
|
|||
function Game(gameDiv, stickyNodes, ballOpts) { |
|||
var stickyNodes, player1, physicsInterval, resizeInterval, listeners = []; |
|||
|
|||
player1 = new PlayerBall(gameDiv, stickyNodes, ballOpts, false); |
|||
player1.init(); |
|||
player1.setXY(300, 300); |
|||
window.scrollTo(0, 200); |
|||
|
|||
function on_resize() { |
|||
player1.setDocSize(jQuery(document).width() - 5, |
|||
jQuery(document).height() - 5); |
|||
} |
|||
on_resize(); |
|||
|
|||
player1.setAccel(true) |
|||
|
|||
// if (ballOpts.MOUSEB === -5) {
|
|||
document.addEventListener('touchstart', function (event) { |
|||
if (event.touches.length === 1) { |
|||
player1.setAccel(true); |
|||
return preventDefault(event); |
|||
} |
|||
}, true); |
|||
document.addEventListener('touchmove', function (event) { |
|||
player1.setAccelTarget(event.touches[0].pageX, |
|||
event.touches[0].pageY); |
|||
}, true); |
|||
document.addEventListener('touchend', function (event) { |
|||
if (event.touches.length === 0) { |
|||
player1.setAccel(false); |
|||
return preventDefault(event); |
|||
} |
|||
}, true); |
|||
// }
|
|||
|
|||
// else {
|
|||
// /* mouse buttons */
|
|||
// document.addEventListener('mousemove', function (event) {
|
|||
// player1.setAccelTarget(event.pageX, event.pageY);
|
|||
// }, true);
|
|||
// document.addEventListener('mousedown', function (event) {
|
|||
// if (event.button === ballOpts.MOUSEB) {
|
|||
// player1.setAccel(true);
|
|||
// return preventDefault(event);
|
|||
// }
|
|||
// }, true);
|
|||
// document.addEventListener('mouseup', function (event) {
|
|||
// if (event.button === ballOpts.MOUSEB) {
|
|||
// player1.setAccel(false);
|
|||
// return preventDefault(event);
|
|||
// }
|
|||
// }, true);
|
|||
// }
|
|||
|
|||
physicsInterval = setInterval(function () { |
|||
player1.accelTargetX = Math.random()*window.innerHeight; |
|||
player1.accelTargetY = Math.random()*window.innerWidth; |
|||
player1.updatePhysics(); |
|||
}, 25); |
|||
resizeInterval = setInterval(on_resize, 1000); |
|||
} |
|||
|
|||
function whenAllLoaded(gameDiv, stickyNodes) { |
|||
stickyNodes.finalize(jQuery(document).width(), jQuery(document).height()); |
|||
|
|||
var game, ballOpts; |
|||
ballOpts = { |
|||
color: 0x00FF00, |
|||
VOL_MULT: 1.0, |
|||
MAX_ATTACHED_VISIBLE: 9000, |
|||
CHECK_VOLS: false, |
|||
MOUSEB: 0 // 0 FOR DESKTOP OR -5 FOR MOBILE
|
|||
}; |
|||
game = new Game(gameDiv, stickyNodes, ballOpts); |
|||
|
|||
// Download image
|
|||
var btn = document.getElementById('imgButton'); |
|||
btn.addEventListener("touchend", function(event) { |
|||
console.log("capture", document.getElementById("capture")); |
|||
html2canvas(document.getElementById("capture")).then(function(canvas) { |
|||
var link = document.createElement("a"); |
|||
document.body.appendChild(link); |
|||
link.download = "ridindirtyberlin.png"; |
|||
link.href = canvas.toDataURL("image/png"); |
|||
link.target = '_blank'; |
|||
link.click(); |
|||
}); |
|||
}) |
|||
|
|||
startPeristentVibrate(300, 430); |
|||
} |
|||
|
|||
function main() { |
|||
var gameDiv, checkInterval, stickyNodes, dlbtn; |
|||
|
|||
gameDiv = document.createElement('div'); |
|||
gameDiv.khIgnore = true; |
|||
dlbtn = document.getElementById("button-wrapper"); |
|||
dlbtn.khIgnore = true; |
|||
|
|||
document.body.appendChild(gameDiv); |
|||
|
|||
setTimeout(function () { |
|||
var i, len, el; |
|||
window.khNodes.addWords(document.body); |
|||
for (i = 0, len = document.body.childNodes.length; i < len; i++) { |
|||
el = document.body.childNodes[i]; |
|||
window.khNodes.addTagNames(el, [ |
|||
'canvas', 'iframe', 'img', 'input', 'select', |
|||
'textarea', 'video', 'marquee', 'progress' |
|||
]); |
|||
} |
|||
|
|||
checkInterval = setInterval(function () { |
|||
if (window.jQuery) { |
|||
clearInterval(checkInterval); |
|||
whenAllLoaded(gameDiv, window.khNodes); |
|||
} |
|||
}, 100); |
|||
|
|||
}, 0); |
|||
} |
|||
|
|||
if (!window.noMain) { |
|||
main(); |
|||
|
|||
var sound = new Howl({ |
|||
src: ['snd/gabba140.mp3'], |
|||
autoplay: true, |
|||
loop: true, |
|||
volume: 1 |
|||
}); |
|||
} |
@ -0,0 +1,98 @@ |
|||
// Captive portal with (arduino) OTA + SPIFFS
|
|||
|
|||
#include <Arduino.h> |
|||
#include <ESP8266WiFi.h> |
|||
#include <ESP8266mDNS.h> |
|||
#include <WiFiUdp.h> |
|||
#include <ArduinoOTA.h> // Over-the-Air updates |
|||
#include <ESP8266WebServer.h> |
|||
#include "./DNSServer.h" // Dns server |
|||
#include <FS.h> // SPIFFS |
|||
|
|||
DNSServer dnsServer; |
|||
const byte DNS_PORT = 53; |
|||
|
|||
ESP8266WebServer server(80); |
|||
|
|||
#ifndef STASSID |
|||
#define STASSID "Plague Rave 2020" |
|||
//#define STASPSK "mypassword"
|
|||
#endif |
|||
|
|||
IPAddress apIP(192, 168, 4, 1); |
|||
const char* ssid = STASSID; |
|||
//const char* password = STAPSK;
|
|||
|
|||
void setup() { |
|||
Serial.begin(115200); |
|||
Serial.println("Booting"); |
|||
|
|||
WiFi.mode(WIFI_AP); |
|||
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); |
|||
WiFi.softAP(ssid); |
|||
dnsServer.start(DNS_PORT, "*", apIP); // redirect dns request to AP ip
|
|||
|
|||
MDNS.begin("opencoil", WiFi.softAPIP()); |
|||
Serial.println("Ready"); |
|||
Serial.print("IP address: "); |
|||
Serial.println(WiFi.softAPIP()); |
|||
|
|||
//Over-the-Air updates
|
|||
ArduinoOTA.setHostname("opencoil"); |
|||
//ArduinoOTA.setPassword("change-me"); //disabled to allow data uploads
|
|||
ArduinoOTA.begin(); |
|||
SPIFFS.begin(); |
|||
|
|||
//redirect all traffic to index.html
|
|||
server.onNotFound([]() { |
|||
if(!handleFileRead(server.uri())){ |
|||
const char *metaRefreshStr = "<head><meta http-equiv=\"refresh\" content=\"0; url=http://192.168.4.1/index.html\" /></head><body><p>redirecting...</p></body>"; |
|||
server.send(200, "text/html", metaRefreshStr); |
|||
} |
|||
}); |
|||
|
|||
server.begin(); |
|||
|
|||
} |
|||
|
|||
void loop() { |
|||
dnsServer.processNextRequest(); |
|||
ArduinoOTA.handle(); |
|||
server.handleClient(); |
|||
delay(50); |
|||
} |
|||
|
|||
|
|||
String getContentType(String filename){ |
|||
if(server.hasArg("download")) return "application/octet-stream"; |
|||
else if(filename.endsWith(".htm")) return "text/html"; |
|||
else if(filename.endsWith(".html")) return "text/html"; |
|||
else if(filename.endsWith(".css")) return "text/css"; |
|||
else if(filename.endsWith(".js")) return "application/javascript"; |
|||
else if(filename.endsWith(".png")) return "image/png"; |
|||
else if(filename.endsWith(".gif")) return "image/gif"; |
|||
else if(filename.endsWith(".jpg")) return "image/jpeg"; |
|||
else if(filename.endsWith(".ico")) return "image/x-icon"; |
|||
else if(filename.endsWith(".xml")) return "text/xml"; |
|||
else if(filename.endsWith(".mp4")) return "video/mp4"; |
|||
else if(filename.endsWith(".pdf")) return "application/x-pdf"; |
|||
else if(filename.endsWith(".zip")) return "application/x-zip"; |
|||
else if(filename.endsWith(".gz")) return "application/x-gzip"; |
|||
return "text/plain"; |
|||
} |
|||
|
|||
//Given a file path, look for it in the SPIFFS file storage. Returns true if found, returns false if not found.
|
|||
bool handleFileRead(String path){ |
|||
if(path.endsWith("/")) path += "index.html"; |
|||
String contentType = getContentType(path); |
|||
String pathWithGz = path + ".gz"; |
|||
if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){ |
|||
if(SPIFFS.exists(pathWithGz)) |
|||
path += ".gz"; |
|||
File file = SPIFFS.open(path, "r"); |
|||
size_t sent = server.streamFile(file, contentType); |
|||
file.close(); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |